diff options
author | Zach Reizner <zachr@google.com> | 2019-04-16 15:09:20 -0700 |
---|---|---|
committer | Commit Bot <commit-bot@chromium.org> | 2019-07-25 22:15:48 +0000 |
commit | f5285c647acacb4f25ef8cf9334254b976e71686 (patch) | |
tree | fcc238ec97736727a9c18b3b9de29be3dce3983e | |
parent | b2110bef59d72529d99c722df9b3e9a1d705e6f4 (diff) | |
download | crosvm-f5285c647acacb4f25ef8cf9334254b976e71686.tar crosvm-f5285c647acacb4f25ef8cf9334254b976e71686.tar.gz crosvm-f5285c647acacb4f25ef8cf9334254b976e71686.tar.bz2 crosvm-f5285c647acacb4f25ef8cf9334254b976e71686.tar.lz crosvm-f5285c647acacb4f25ef8cf9334254b976e71686.tar.xz crosvm-f5285c647acacb4f25ef8cf9334254b976e71686.tar.zst crosvm-f5285c647acacb4f25ef8cf9334254b976e71686.zip |
gpu_display: add X11 backend
This change adds an X11 backend to the gpu_display crate. With this addition, the virtio-gpu device can display to traditional linux desktops that only have X11 output. Change-Id: I86c80cac91ca5bdc97588194a44040273ae69385 Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/platform/crosvm/+/1591572 Reviewed-by: Stéphane Marchesin <marcheu@chromium.org> Commit-Queue: Zach Reizner <zachr@chromium.org> Tested-by: Zach Reizner <zachr@chromium.org> Tested-by: kokoro <noreply+kokoro@google.com> Auto-Submit: Zach Reizner <zachr@chromium.org>
-rw-r--r-- | Cargo.toml | 1 | ||||
-rw-r--r-- | devices/Cargo.toml | 1 | ||||
-rw-r--r-- | devices/src/virtio/gpu/backend.rs | 145 | ||||
-rw-r--r-- | devices/src/virtio/gpu/mod.rs | 198 | ||||
-rw-r--r-- | gpu_buffer/src/lib.rs | 14 | ||||
-rw-r--r-- | gpu_display/Cargo.toml | 3 | ||||
-rw-r--r-- | gpu_display/examples/simple.rs | 4 | ||||
-rw-r--r-- | gpu_display/examples/simple_open.rs | 25 | ||||
-rw-r--r-- | gpu_display/src/generated/xlib.rs | 892 | ||||
-rwxr-xr-x | gpu_display/src/generated/xlib_generator.sh | 76 | ||||
-rw-r--r-- | gpu_display/src/generated/xlib_wrapper.h | 4 | ||||
-rw-r--r-- | gpu_display/src/gpu_display_wl.rs | 347 | ||||
-rw-r--r-- | gpu_display/src/gpu_display_x.rs | 647 | ||||
-rw-r--r-- | gpu_display/src/lib.rs | 404 | ||||
-rw-r--r-- | gpu_renderer/src/lib.rs | 281 |
15 files changed, 2594 insertions, 448 deletions
diff --git a/Cargo.toml b/Cargo.toml index 048681f..eda21cf 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -28,6 +28,7 @@ plugin = ["protos/plugin", "crosvm_plugin", "protobuf"] sandboxed-libusb = ["devices/sandboxed-libusb", "vm_control/sandboxed-libusb"] tpm = ["devices/tpm"] wl-dmabuf = ["devices/wl-dmabuf", "gpu_buffer", "resources/wl-dmabuf"] +x = ["devices/x"] [dependencies] arch = { path = "arch" } diff --git a/devices/Cargo.toml b/devices/Cargo.toml index 8cfe8dd..3e1e554 100644 --- a/devices/Cargo.toml +++ b/devices/Cargo.toml @@ -9,6 +9,7 @@ gpu = ["gpu_buffer", "gpu_display", "gpu_renderer"] sandboxed-libusb = ["usb_util/sandboxed-libusb"] tpm = ["protos/trunks", "tpm2"] wl-dmabuf = [] +x = ["gpu_display/x"] [dependencies] audio_streams = "*" diff --git a/devices/src/virtio/gpu/backend.rs b/devices/src/virtio/gpu/backend.rs index 8f693cc..8dd7806 100644 --- a/devices/src/virtio/gpu/backend.rs +++ b/devices/src/virtio/gpu/backend.rs @@ -76,7 +76,15 @@ trait VirglResource { ); /// Reads from the given rectangle of pixels in the resource to the `dst` slice of memory. - fn read_to_volatile(&mut self, x: u32, y: u32, width: u32, height: u32, dst: VolatileSlice); + fn read_to_volatile( + &mut self, + x: u32, + y: u32, + width: u32, + height: u32, + dst: VolatileSlice, + dst_stride: u32, + ); } impl VirglResource for GpuRendererResource { @@ -125,12 +133,20 @@ impl VirglResource for GpuRendererResource { } } - fn read_to_volatile(&mut self, x: u32, y: u32, width: u32, height: u32, dst: VolatileSlice) { + fn read_to_volatile( + &mut self, + x: u32, + y: u32, + width: u32, + height: u32, + dst: VolatileSlice, + dst_stride: u32, + ) { let res = GpuRendererResource::read_to_volatile( self, None, 0, - 0, + dst_stride, 0, /* layer_stride */ Box3::new_2d(x, y, width, height), 0, /* offset */ @@ -278,8 +294,19 @@ impl VirglResource for BackedBuffer { } } - fn read_to_volatile(&mut self, x: u32, y: u32, width: u32, height: u32, dst: VolatileSlice) { - if let Err(e) = self.buffer.read_to_volatile(x, y, width, height, 0, dst) { + fn read_to_volatile( + &mut self, + x: u32, + y: u32, + width: u32, + height: u32, + dst: VolatileSlice, + dst_stride: u32, + ) { + if let Err(e) = self + .buffer + .read_to_volatile(x, y, width, height, 0, dst, dst_stride) + { error!("failed to copy resource: {}", e); } } @@ -292,7 +319,7 @@ impl VirglResource for BackedBuffer { /// failure, or requested data for the given command. pub struct Backend { display: Rc<RefCell<GpuDisplay>>, - device: Device, + device: Option<Device>, renderer: Renderer, resources: Map<u32, Box<dyn VirglResource>>, contexts: Map<u32, RendererContext>, @@ -308,8 +335,11 @@ impl Backend { /// Creates a new backend for virtio-gpu that realizes all commands using the given `device` for /// allocating buffers, `display` for showing the results, and `renderer` for submitting /// rendering commands. + /// + /// If the `device` is None, all buffer allocations will be done internally by the renderer or + /// the display and buffer data is copied as needed. pub fn new( - device: Device, + device: Option<Device>, display: GpuDisplay, renderer: Renderer, gpu_device_socket: VmMemoryControlRequestSocket, @@ -377,30 +407,54 @@ impl Backend { id: u32, width: u32, height: u32, - fourcc: u32, + format: u32, ) -> GpuResponse { if id == 0 { return GpuResponse::ErrInvalidResourceId; } match self.resources.entry(id) { - Entry::Vacant(slot) => { - let res = self.device.create_buffer( - width, - height, - Format::from(fourcc), - Flags::empty().use_scanout(true).use_linear(true), - ); - match res { - Ok(res) => { - slot.insert(Box::from(BackedBuffer::from(res))); - GpuResponse::OkNoData + Entry::Vacant(slot) => match self.device.as_ref() { + Some(device) => match renderer_fourcc(format) { + Some(fourcc) => { + let res = device.create_buffer( + width, + height, + Format::from(fourcc), + Flags::empty().use_scanout(true).use_linear(true), + ); + match res { + Ok(res) => { + slot.insert(Box::from(BackedBuffer::from(res))); + GpuResponse::OkNoData + } + Err(_) => { + error!("failed to create renderer resource {}", fourcc); + GpuResponse::ErrUnspec + } + } + } + None => { + error!( + "unrecognized format can not be converted to fourcc {}", + format + ); + GpuResponse::ErrInvalidParameter } - Err(_) => { - error!("failed to create renderer resource {}", fourcc); - GpuResponse::ErrUnspec + }, + None => { + let res = self.renderer.create_resource_2d(id, width, height, format); + match res { + Ok(res) => { + slot.insert(Box::new(res)); + GpuResponse::OkNoData + } + Err(e) => { + error!("failed to create renderer resource: {}", e); + GpuResponse::ErrUnspec + } } } - } + }, Entry::Occupied(_) => GpuResponse::ErrInvalidResourceId, } } @@ -445,10 +499,10 @@ impl Backend { &mut self, resource_id: u32, surface_id: u32, - x: u32, - y: u32, - width: u32, - height: u32, + _x: u32, + _y: u32, + _width: u32, + _height: u32, ) -> GpuResponse { let resource = match self.resources.get_mut(&resource_id) { Some(r) => r, @@ -461,12 +515,13 @@ impl Backend { } // Import failed, fall back to a copy. - let display = self.display.borrow_mut(); + let mut display = self.display.borrow_mut(); // Prevent overwriting a buffer that is currently being used by the compositor. if display.next_buffer_in_use(surface_id) { return GpuResponse::OkNoData; } - let fb = match display.framebuffer_memory(surface_id) { + + let fb = match display.framebuffer_region(surface_id, 0, 0, DEFAULT_WIDTH, DEFAULT_HEIGHT) { Some(fb) => fb, None => { error!("failed to access framebuffer for surface {}", surface_id); @@ -474,7 +529,14 @@ impl Backend { } }; - resource.read_to_volatile(x, y, width, height, fb); + resource.read_to_volatile( + 0, + 0, + DEFAULT_WIDTH, + DEFAULT_HEIGHT, + fb.as_volatile_slice(), + fb.stride(), + ); display.flip(surface_id); GpuResponse::OkNoData @@ -599,10 +661,16 @@ impl Backend { // Importing failed, so try copying the pixels into the surface's slower shared memory // framebuffer. if let Some(buffer) = resource.buffer() { - if let Some(fb) = self.display.borrow_mut().framebuffer_memory(cursor_surface) { - if let Err(e) = - buffer.read_to_volatile(0, 0, buffer.width(), buffer.height(), 0, fb) - { + if let Some(fb) = self.display.borrow_mut().framebuffer(cursor_surface) { + if let Err(e) = buffer.read_to_volatile( + 0, + 0, + buffer.width(), + buffer.height(), + 0, + fb.as_volatile_slice(), + fb.stride(), + ) { error!("failed to copy resource to cursor: {}", e); return GpuResponse::ErrInvalidParameter; } @@ -619,7 +687,7 @@ impl Backend { pub fn move_cursor(&mut self, x: u32, y: u32) -> GpuResponse { if let Some(cursor_surface) = self.cursor_surface { if let Some(scanout_surface) = self.scanout_surface { - let display = self.display.borrow_mut(); + let mut display = self.display.borrow_mut(); display.set_position(cursor_surface, x, y); display.commit(scanout_surface); } @@ -751,10 +819,11 @@ impl Backend { match self.resources.entry(id) { Entry::Occupied(_) => GpuResponse::ErrInvalidResourceId, Entry::Vacant(slot) => { - if Backend::allocate_using_minigbm(create_args) { + if self.device.is_some() && Backend::allocate_using_minigbm(create_args) { + let device = self.device.as_ref().unwrap(); match renderer_fourcc(create_args.format) { Some(fourcc) => { - let buffer = match self.device.create_buffer( + let buffer = match device.create_buffer( width, height, Format::from(fourcc), @@ -763,7 +832,7 @@ impl Backend { Ok(buffer) => buffer, Err(_) => { // Attempt to allocate the buffer without scanout flag. - match self.device.create_buffer( + match device.create_buffer( width, height, Format::from(fourcc), diff --git a/devices/src/virtio/gpu/mod.rs b/devices/src/virtio/gpu/mod.rs index 6cf60d9..c81afa5 100644 --- a/devices/src/virtio/gpu/mod.rs +++ b/devices/src/virtio/gpu/mod.rs @@ -9,6 +9,7 @@ use std::cell::RefCell; use std::collections::VecDeque; use std::i64; use std::mem::{self, size_of}; +use std::num::NonZeroU8; use std::os::unix::io::{AsRawFd, RawFd}; use std::path::{Path, PathBuf}; use std::rc::Rc; @@ -25,7 +26,7 @@ use sys_util::{ use gpu_buffer::Device; use gpu_display::*; -use gpu_renderer::{format_fourcc, Renderer}; +use gpu_renderer::{Renderer, RendererFlags}; use super::{ resource_bridge::*, AvailIter, Queue, VirtioDevice, INTERRUPT_STATUS_USED_RING, TYPE_GPU, @@ -107,24 +108,12 @@ impl Frontend { GpuCommand::GetDisplayInfo(_) => { GpuResponse::OkDisplayInfo(self.backend.display_info().to_vec()) } - GpuCommand::ResourceCreate2d(info) => { - let format = info.format.to_native(); - match format_fourcc(format) { - Some(fourcc) => self.backend.create_resource_2d( - info.resource_id.to_native(), - info.width.to_native(), - info.height.to_native(), - fourcc, - ), - None => { - warn!( - "failed to create resource with unrecognized pipe format {}", - format - ); - GpuResponse::ErrInvalidParameter - } - } - } + GpuCommand::ResourceCreate2d(info) => self.backend.create_resource_2d( + info.resource_id.to_native(), + info.width.to_native(), + info.height.to_native(), + info.format.to_native(), + ), GpuCommand::ResourceUnref(info) => { self.backend.unref_resource(info.resource_id.to_native()) } @@ -602,13 +591,123 @@ impl Worker { } } +/// Indicates a backend that should be tried for the gpu to use for display. +/// +/// Several instances of this enum are used in an ordered list to give the gpu device many backends +/// to use as fallbacks in case some do not work. +#[derive(Clone)] +pub enum DisplayBackend { + /// Use the wayland backend with the given socket path if given. + Wayland(Option<PathBuf>), + /// Open a connection to the X server at the given display if given. + X(Option<String>), + /// Emulate a display without actually displaying it. + Null, +} + +impl DisplayBackend { + fn build(&self) -> std::result::Result<GpuDisplay, GpuDisplayError> { + match self { + DisplayBackend::Wayland(path) => GpuDisplay::open_wayland(path.as_ref()), + DisplayBackend::X(display) => GpuDisplay::open_x(display.as_ref()), + DisplayBackend::Null => unimplemented!(), + } + } + + fn is_x(&self) -> bool { + match self { + DisplayBackend::X(_) => true, + _ => false, + } + } +} + +/// Builds a Device for doing buffer allocation and sharing via dmabuf. +fn build_buffer_device() -> Option<Device> { + const UNDESIRED_CARDS: &[&str] = &["vgem", "pvr"]; + let drm_card = match gpu_buffer::rendernode::open_device(UNDESIRED_CARDS) { + Ok(f) => f, + Err(()) => { + error!("failed to open render node for GBM"); + return None; + } + }; + match Device::new(drm_card) { + Ok(d) => Some(d), + Err(()) => { + error!("failed to create GBM device from render node"); + None + } + } +} + +// Builds a gpu backend with one of the given possible display backends, or None if they all +// failed. +fn build_backend( + possible_displays: &[DisplayBackend], + gpu_device_socket: VmMemoryControlRequestSocket, +) -> Option<Backend> { + let mut buffer_device = None; + let mut renderer_flags = RendererFlags::default(); + let mut display_opt = None; + for display in possible_displays { + match display.build() { + Ok(c) => { + // If X11 is being used, that's an indication that the renderer should also be using + // glx. Otherwise, we are likely in an enviroment in which GBM will work for doing + // allocations of buffers we wish to display. TODO(zachr): this is a heuristic (or + // terrible hack depending on your POV). We should do something either smarter or + // more configurable + if display.is_x() { + renderer_flags = RendererFlags::new().use_glx(true); + } else { + buffer_device = build_buffer_device(); + } + display_opt = Some(c); + break; + } + Err(e) => error!("failed to open display: {}", e), + }; + } + let display = match display_opt { + Some(d) => d, + None => { + error!("failed to open any displays"); + return None; + } + }; + + if cfg!(debug_assertions) { + let ret = unsafe { libc::dup2(libc::STDOUT_FILENO, libc::STDERR_FILENO) }; + if ret == -1 { + warn!("unable to dup2 stdout to stderr: {}", Error::last()); + } + } + + let renderer = match Renderer::init(renderer_flags) { + Ok(r) => r, + Err(e) => { + error!("failed to initialize gpu renderer: {}", e); + return None; + } + }; + + Some(Backend::new( + buffer_device, + display, + renderer, + gpu_device_socket, + )) +} + pub struct Gpu { config_event: bool, exit_evt: EventFd, gpu_device_socket: Option<VmMemoryControlRequestSocket>, resource_bridges: Vec<ResourceResponseSocket>, kill_evt: Option<EventFd>, - wayland_socket_path: PathBuf, + num_scanouts: NonZeroU8, + display_backends: Vec<DisplayBackend>, } impl Gpu { @@ -618,13 +717,17 @@ impl Gpu { resource_bridges: Vec<ResourceResponseSocket>, wayland_socket_path: P, ) -> Gpu { + let display_backends = vec![DisplayBackend::Wayland(Some( + wayland_socket_path.as_ref().to_owned(), + ))]; Gpu { config_event: false, exit_evt, gpu_device_socket, resource_bridges, kill_evt: None, - wayland_socket_path: wayland_socket_path.as_ref().to_path_buf(), + num_scanouts: NonZeroU8::new(1).unwrap(), + display_backends, } } @@ -636,7 +739,7 @@ impl Gpu { virtio_gpu_config { events_read: Le32::from(events_read), events_clear: Le32::from(0), - num_scanouts: Le32::from(1), + num_scanouts: Le32::from(self.num_scanouts.get() as u32), num_capsets: Le32::from(3), } } @@ -747,51 +850,15 @@ impl VirtioDevice for Gpu { let ctrl_evt = queue_evts.remove(0); let cursor_queue = queues.remove(0); let cursor_evt = queue_evts.remove(0); - let socket_path = self.wayland_socket_path.clone(); + let display_backends = self.display_backends.clone(); if let Some(gpu_device_socket) = self.gpu_device_socket.take() { let worker_result = thread::Builder::new() .name("virtio_gpu".to_string()) .spawn(move || { - const UNDESIRED_CARDS: &[&str] = &["vgem", "pvr"]; - let drm_card = match gpu_buffer::rendernode::open_device(UNDESIRED_CARDS) { - Ok(f) => f, - Err(()) => { - error!("failed to open card"); - return; - } - }; - - let device = match Device::new(drm_card) { - Ok(d) => d, - Err(()) => { - error!("failed to open device"); - return; - } - }; - - let display = match GpuDisplay::new(socket_path) { - Ok(c) => c, - Err(e) => { - error!("failed to open display: {}", e); - return; - } - }; - - if cfg!(debug_assertions) { - let ret = - unsafe { libc::dup2(libc::STDOUT_FILENO, libc::STDERR_FILENO) }; - if ret == -1 { - warn!("unable to dup2 stdout to stderr: {}", Error::last()); - } - } - - let renderer = match Renderer::init() { - Ok(r) => r, - Err(e) => { - error!("failed to initialize gpu renderer: {}", e); - return; - } + let backend = match build_backend(&display_backends, gpu_device_socket) { + Some(backend) => backend, + None => return, }; Worker { @@ -806,12 +873,7 @@ impl VirtioDevice for Gpu { cursor_evt, resource_bridges, kill_evt, - state: Frontend::new(Backend::new( - device, - display, - renderer, - gpu_device_socket, - )), + state: Frontend::new(backend), } .run() }); diff --git a/gpu_buffer/src/lib.rs b/gpu_buffer/src/lib.rs index 53fca92..08174dc 100644 --- a/gpu_buffer/src/lib.rs +++ b/gpu_buffer/src/lib.rs @@ -566,19 +566,21 @@ impl Buffer { height: u32, plane: usize, dst: VolatileSlice, + dst_stride: u32, ) -> Result<(), Error> { if width == 0 || height == 0 { return Ok(()); } let mapping = self.map(x, y, width, height, plane, GBM_BO_TRANSFER_READ)?; + let src_stride = mapping.stride() as u64; + let dst_stride = dst_stride as u64; - if x == 0 && width == self.width() { + if x == 0 && width == self.width() && src_stride == dst_stride { mapping.as_volatile_slice().copy_to_volatile_slice(dst); } else { // This path is more complicated because there are gaps in the data between lines. let width = width as u64; - let stride = mapping.stride() as u64; let bytes_per_pixel = match self.format().bytes_per_pixel(plane) { Some(bpp) => bpp as u64, None => return Err(Error::UnknownFormat(self.format())), @@ -586,12 +588,13 @@ impl Buffer { let line_copy_size = checked_arithmetic!(width * bytes_per_pixel)?; let src = mapping.as_volatile_slice(); for yy in 0..(height as u64) { - let line_offset = checked_arithmetic!(yy * stride)?; + let src_line_offset = checked_arithmetic!(yy * src_stride)?; + let dst_line_offset = checked_arithmetic!(yy * dst_stride)?; let src_line = src - .get_slice(line_offset, line_copy_size) + .get_slice(src_line_offset, line_copy_size) .map_err(Error::Memcopy)?; let dst_line = dst - .get_slice(line_offset, line_copy_size) + .get_slice(dst_line_offset, line_copy_size) .map_err(Error::Memcopy)?; src_line.copy_to_volatile_slice(dst_line); } @@ -855,6 +858,7 @@ mod tests { 1024, 0, dst.as_mut_slice().get_slice(0, dst_len).unwrap(), + bo.stride(), ) .expect("failed to read bo"); assert!(dst.iter().all(|&x| x == 0x4A)); diff --git a/gpu_display/Cargo.toml b/gpu_display/Cargo.toml index 0177f1e..54f2bad 100644 --- a/gpu_display/Cargo.toml +++ b/gpu_display/Cargo.toml @@ -4,6 +4,9 @@ version = "0.1.0" authors = ["The Chromium OS Authors"] edition = "2018" +[features] +x = [] + [dependencies] data_model = { path = "../data_model" } libc = "*" diff --git a/gpu_display/examples/simple.rs b/gpu_display/examples/simple.rs index 140e6cc..ace9828 100644 --- a/gpu_display/examples/simple.rs +++ b/gpu_display/examples/simple.rs @@ -5,8 +5,10 @@ use gpu_display::*; fn main() { - let mut disp = GpuDisplay::new("/run/wayland-0").unwrap(); + let mut disp = GpuDisplay::open_wayland(None::<&str>).unwrap(); let surface_id = disp.create_surface(None, 1280, 1024).unwrap(); + disp.flip(surface_id); + disp.commit(surface_id); while !disp.close_requested(surface_id) { disp.dispatch_events(); } diff --git a/gpu_display/examples/simple_open.rs b/gpu_display/examples/simple_open.rs new file mode 100644 index 0000000..bb7b1a2 --- /dev/null +++ b/gpu_display/examples/simple_open.rs @@ -0,0 +1,25 @@ +use gpu_display::GpuDisplay; + +fn main() { + let mut disp = GpuDisplay::open_x(None::<&str>).unwrap(); + let surface_id = disp.create_surface(None, 1280, 1024).unwrap(); + + let mem = disp.framebuffer(surface_id).unwrap(); + for y in 0..1024 { + let mut row = [0u32; 1280]; + for x in 0..1280 { + let b = ((x as f32 / 1280.0) * 256.0) as u32; + let g = ((y as f32 / 1024.0) * 256.0) as u32; + row[x] = b | (g << 8); + } + mem.as_volatile_slice() + .offset((1280 * 4 * y) as u64) + .unwrap() + .copy_from(&row); + } + disp.flip(surface_id); + + while !disp.close_requested(surface_id) { + disp.dispatch_events(); + } +} diff --git a/gpu_display/src/generated/xlib.rs b/gpu_display/src/generated/xlib.rs new file mode 100644 index 0000000..bbfa6bd --- /dev/null +++ b/gpu_display/src/generated/xlib.rs @@ -0,0 +1,892 @@ +// 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. + +//! Generated using ./xlib_generator.sh + +#[link(name = "X11")] +extern "C" {} + +#[link(name = "Xext")] +extern "C" {} + +/* automatically generated by rust-bindgen */ + +pub const ExposureMask: u32 = 32768; +pub const Expose: u32 = 12; +pub const ClientMessage: u32 = 33; +pub const ZPixmap: u32 = 2; +pub const PMinSize: u32 = 16; +pub const PMaxSize: u32 = 32; +pub const VisualScreenMask: u32 = 2; +pub const VisualDepthMask: u32 = 4; +pub const VisualRedMaskMask: u32 = 16; +pub const VisualGreenMaskMask: u32 = 32; +pub const VisualBlueMaskMask: u32 = 64; +pub const ShmCompletion: u32 = 0; +pub type XID = ::std::os::raw::c_ulong; +pub type Atom = ::std::os::raw::c_ulong; +pub type VisualID = ::std::os::raw::c_ulong; +pub type Time = ::std::os::raw::c_ulong; +pub type Window = XID; +pub type Drawable = XID; +pub type Font = XID; +pub type Pixmap = XID; +pub type Colormap = XID; +pub type XPointer = *mut ::std::os::raw::c_char; +#[repr(C)] +#[derive(Copy, Clone)] +pub struct _XExtData { + pub number: ::std::os::raw::c_int, + pub next: *mut _XExtData, + pub free_private: ::std::option::Option< + unsafe extern "C" fn(extension: *mut _XExtData) -> ::std::os::raw::c_int, + >, + pub private_data: XPointer, +} +pub type XExtData = _XExtData; +#[repr(C)] +#[derive(Copy, Clone)] +pub struct XGCValues { + pub function: ::std::os::raw::c_int, + pub plane_mask: ::std::os::raw::c_ulong, + pub foreground: ::std::os::raw::c_ulong, + pub background: ::std::os::raw::c_ulong, + pub line_width: ::std::os::raw::c_int, + pub line_style: ::std::os::raw::c_int, + pub cap_style: ::std::os::raw::c_int, + pub join_style: ::std::os::raw::c_int, + pub fill_style: ::std::os::raw::c_int, + pub fill_rule: ::std::os::raw::c_int, + pub arc_mode: ::std::os::raw::c_int, + pub tile: Pixmap, + pub stipple: Pixmap, + pub ts_x_origin: ::std::os::raw::c_int, + pub ts_y_origin: ::std::os::raw::c_int, + pub font: Font, + pub subwindow_mode: ::std::os::raw::c_int, + pub graphics_exposures: ::std::os::raw::c_int, + pub clip_x_origin: ::std::os::raw::c_int, + pub clip_y_origin: ::std::os::raw::c_int, + pub clip_mask: Pixmap, + pub dash_offset: ::std::os::raw::c_int, + pub dashes: ::std::os::raw::c_char, +} +#[repr(C)] +#[derive(Copy, Clone)] +pub struct _XGC { + _unused: [u8; 0], +} +pub type GC = *mut _XGC; +#[repr(C)] +#[derive(Copy, Clone)] +pub struct Visual { + pub ext_data: *mut XExtData, + pub visualid: VisualID, + pub class: ::std::os::raw::c_int, + pub red_mask: ::std::os::raw::c_ulong, + pub green_mask: ::std::os::raw::c_ulong, + pub blue_mask: ::std::os::raw::c_ulong, + pub bits_per_rgb: ::std::os::raw::c_int, + pub map_entries: ::std::os::raw::c_int, +} +#[repr(C)] +#[derive(Copy, Clone)] +pub struct Depth { + pub depth: ::std::os::raw::c_int, + pub nvisuals: ::std::os::raw::c_int, + pub visuals: *mut Visual, +} +#[repr(C)] +#[derive(Copy, Clone)] +pub struct _XDisplay { + _unused: [u8; 0], +} +#[repr(C)] +#[derive(Copy, Clone)] +pub struct Screen { + pub ext_data: *mut XExtData, + pub display: *mut _XDisplay, + pub root: Window, + pub width: ::std::os::raw::c_int, + pub height: ::std::os::raw::c_int, + pub mwidth: ::std::os::raw::c_int, + pub mheight: ::std::os::raw::c_int, + pub ndepths: ::std::os::raw::c_int, + pub depths: *mut Depth, + pub root_depth: ::std::os::raw::c_int, + pub root_visual: *mut Visual, + pub default_gc: GC, + pub cmap: Colormap, + pub white_pixel: ::std::os::raw::c_ulong, + pub black_pixel: ::std::os::raw::c_ulong, + pub max_maps: ::std::os::raw::c_int, + pub min_maps: ::std::os::raw::c_int, + pub backing_store: ::std::os::raw::c_int, + pub save_unders: ::std::os::raw::c_int, + pub root_input_mask: ::std::os::raw::c_long, +} +#[repr(C)] +#[derive(Copy, Clone)] +pub struct _XImage { + pub width: ::std::os::raw::c_int, + pub height: ::std::os::raw::c_int, + pub xoffset: ::std::os::raw::c_int, + pub format: ::std::os::raw::c_int, + pub data: *mut ::std::os::raw::c_char, + pub byte_order: ::std::os::raw::c_int, + pub bitmap_unit: ::std::os::raw::c_int, + pub bitmap_bit_order: ::std::os::raw::c_int, + pub bitmap_pad: ::std::os::raw::c_int, + pub depth: ::std::os::raw::c_int, + pub bytes_per_line: ::std::os::raw::c_int, + pub bits_per_pixel: ::std::os::raw::c_int, + pub red_mask: ::std::os::raw::c_ulong, + pub green_mask: ::std::os::raw::c_ulong, + pub blue_mask: ::std::os::raw::c_ulong, + pub obdata: XPointer, + pub f: _XImage_funcs, +} +#[repr(C)] +#[derive(Copy, Clone)] +pub struct _XImage_funcs { + pub create_image: ::std::option::Option< + unsafe extern "C" fn( + arg1: *mut _XDisplay, + arg2: *mut Visual, + arg3: ::std::os::raw::c_uint, + arg4: ::std::os::raw::c_int, + arg5: ::std::os::raw::c_int, + arg6: *mut ::std::os::raw::c_char, + arg7: ::std::os::raw::c_uint, + arg8: ::std::os::raw::c_uint, + arg9: ::std::os::raw::c_int, + arg10: ::std::os::raw::c_int, + ) -> *mut _XImage, + >, + pub destroy_image: + ::std::option::Option<unsafe extern "C" fn(arg1: *mut _XImage) -> ::std::os::raw::c_int>, + pub get_pixel: ::std::option::Option< + unsafe extern "C" fn( + arg1: *mut _XImage, + arg2: ::std::os::raw::c_int, + arg3: ::std::os::raw::c_int, + ) -> ::std::os::raw::c_ulong, + >, + pub put_pixel: ::std::option::Option< + unsafe extern "C" fn( + arg1: *mut _XImage, + arg2: ::std::os::raw::c_int, + arg3: ::std::os::raw::c_int, + arg4: ::std::os::raw::c_ulong, + ) -> ::std::os::raw::c_int, + >, + pub sub_image: ::std::option::Option< + unsafe extern "C" fn( + arg1: *mut _XImage, + arg2: ::std::os::raw::c_int, + arg3: ::std::os::raw::c_int, + arg4: ::std::os::raw::c_uint, + arg5: ::std::os::raw::c_uint, + ) -> *mut _XImage, + >, + pub add_pixel: ::std::option::Option< + unsafe extern "C" fn( + arg1: *mut _XImage, + arg2: ::std::os::raw::c_long, + ) -> ::std::os::raw::c_int, + >, +} +pub type XImage = _XImage; +pub type Display = _XDisplay; +#[repr(C)] +#[derive(Copy, Clone)] +pub struct XKeyEvent { + pub type_: ::std::os::raw::c_int, + pub serial: ::std::os::raw::c_ulong, + pub send_event: ::std::os::raw::c_int, + pub display: *mut Display, + pub window: Window, + pub root: Window, + pub subwindow: Window, + pub time: Time, + pub x: ::std::os::raw::c_int, + pub y: ::std::os::raw::c_int, + pub x_root: ::std::os::raw::c_int, + pub y_root: ::std::os::raw::c_int, + pub state: ::std::os::raw::c_uint, + pub keycode: ::std::os::raw::c_uint, + pub same_screen: ::std::os::raw::c_int, +} +#[repr(C)] +#[derive(Copy, Clone)] +pub struct XButtonEvent { + pub type_: ::std::os::raw::c_int, + pub serial: ::std::os::raw::c_ulong, + pub send_event: ::std::os::raw::c_int, + pub display: *mut Display, + pub window: Window, + pub root: Window, + pub subwindow: Window, + pub time: Time, + pub x: ::std::os::raw::c_int, + pub y: ::std::os::raw::c_int, + pub x_root: ::std::os::raw::c_int, + pub y_root: ::std::os::raw::c_int, + pub state: ::std::os::raw::c_uint, + pub button: ::std::os::raw::c_uint, + pub same_screen: ::std::os::raw::c_int, +} +#[repr(C)] +#[derive(Copy, Clone)] +pub struct XMotionEvent { + pub type_: ::std::os::raw::c_int, + pub serial: ::std::os::raw::c_ulong, + pub send_event: ::std::os::raw::c_int, + pub display: *mut Display, + pub window: Window, + pub root: Window, + pub subwindow: Window, + pub time: Time, + pub x: ::std::os::raw::c_int, + pub y: ::std::os::raw::c_int, + pub x_root: ::std::os::raw::c_int, + pub y_root: ::std::os::raw::c_int, + pub state: ::std::os::raw::c_uint, + pub is_hint: ::std::os::raw::c_char, + pub same_screen: ::std::os::raw::c_int, +} +#[repr(C)] +#[derive(Copy, Clone)] +pub struct XCrossingEvent { + pub type_: ::std::os::raw::c_int, + pub serial: ::std::os::raw::c_ulong, + pub send_event: ::std::os::raw::c_int, + pub display: *mut Display, + pub window: Window, + pub root: Window, + pub subwindow: Window, + pub time: Time, + pub x: ::std::os::raw::c_int, + pub y: ::std::os::raw::c_int, + pub x_root: ::std::os::raw::c_int, + pub y_root: ::std::os::raw::c_int, + pub mode: ::std::os::raw::c_int, + pub detail: ::std::os::raw::c_int, + pub same_screen: ::std::os::raw::c_int, + pub focus: ::std::os::raw::c_int, + pub state: ::std::os::raw::c_uint, +} +#[repr(C)] +#[derive(Copy, Clone)] +pub struct XFocusChangeEvent { + pub type_: ::std::os::raw::c_int, + pub serial: ::std::os::raw::c_ulong, + pub send_event: ::std::os::raw::c_int, + pub display: *mut Display, + pub window: Window, + pub mode: ::std::os::raw::c_int, + pub detail: ::std::os::raw::c_int, +} +#[repr(C)] +#[derive(Copy, Clone)] +pub struct XKeymapEvent { + pub type_: ::std::os::raw::c_int, + pub serial: ::std::os::raw::c_ulong, + pub send_event: ::std::os::raw::c_int, + pub display: *mut Display, + pub window: Window, + pub key_vector: [::std::os::raw::c_char; 32usize], +} +#[repr(C)] +#[derive(Copy, Clone)] +pub struct XExposeEvent { + pub type_: ::std::os::raw::c_int, + pub serial: ::std::os::raw::c_ulong, + pub send_event: ::std::os::raw::c_int, + pub display: *mut Display, + pub window: Window, + pub x: ::std::os::raw::c_int, + pub y: ::std::os::raw::c_int, + pub width: ::std::os::raw::c_int, + pub height: ::std::os::raw::c_int, + pub count: ::std::os::raw::c_int, +} +#[repr(C)] +#[derive(Copy, Clone)] +pub struct XGraphicsExposeEvent { + pub type_: ::std::os::raw::c_int, + pub serial: ::std::os::raw::c_ulong, + pub send_event: ::std::os::raw::c_int, + pub display: *mut Display, + pub drawable: Drawable, + pub x: ::std::os::raw::c_int, + pub y: ::std::os::raw::c_int, + pub width: ::std::os::raw::c_int, + pub height: ::std::os::raw::c_int, + pub count: ::std::os::raw::c_int, + pub major_code: ::std::os::raw::c_int, + pub minor_code: ::std::os::raw::c_int, +} +#[repr(C)] +#[derive(Copy, Clone)] +pub struct XNoExposeEvent { + pub type_: ::std::os::raw::c_int, + pub serial: ::std::os::raw::c_ulong, + pub send_event: ::std::os::raw::c_int, + pub display: *mut Display, + pub drawable: Drawable, + pub major_code: ::std::os::raw::c_int, + pub minor_code: ::std::os::raw::c_int, +} +#[repr(C)] +#[derive(Copy, Clone)] +pub struct XVisibilityEvent { + pub type_: ::std::os::raw::c_int, + pub serial: ::std::os::raw::c_ulong, + pub send_event: ::std::os::raw::c_int, + pub display: *mut Display, + pub window: Window, + pub state: ::std::os::raw::c_int, +} +#[repr(C)] +#[derive(Copy, Clone)] +pub struct XCreateWindowEvent { + pub type_: ::std::os::raw::c_int, + pub serial: ::std::os::raw::c_ulong, + pub send_event: ::std::os::raw::c_int, + pub display: *mut Display, + pub parent: Window, + pub window: Window, + pub x: ::std::os::raw::c_int, + pub y: ::std::os::raw::c_int, + pub width: ::std::os::raw::c_int, + pub height: ::std::os::raw::c_int, + pub border_width: ::std::os::raw::c_int, + pub override_redirect: ::std::os::raw::c_int, +} +#[repr(C)] +#[derive(Copy, Clone)] +pub struct XDestroyWindowEvent { + pub type_: ::std::os::raw::c_int, + pub serial: ::std::os::raw::c_ulong, + pub send_event: ::std::os::raw::c_int, + pub display: *mut Display, + pub event: Window, + pub window: Window, +} +#[repr(C)] +#[derive(Copy, Clone)] +pub struct XUnmapEvent { + pub type_: ::std::os::raw::c_int, + pub serial: ::std::os::raw::c_ulong, + pub send_event: ::std::os::raw::c_int, + pub display: *mut Display, + pub event: Window, + pub window: Window, + pub from_configure: ::std::os::raw::c_int, +} +#[repr(C)] +#[derive(Copy, Clone)] +pub struct XMapEvent { + pub type_: ::std::os::raw::c_int, + pub serial: ::std::os::raw::c_ulong, + pub send_event: ::std::os::raw::c_int, + pub display: *mut Display, + pub event: Window, + pub window: Window, + pub override_redirect: ::std::os::raw::c_int, +} +#[repr(C)] +#[derive(Copy, Clone)] +pub struct XMapRequestEvent { + pub type_: ::std::os::raw::c_int, + pub serial: ::std::os::raw::c_ulong, + pub send_event: ::std::os::raw::c_int, + pub display: *mut Display, + pub parent: Window, + pub window: Window, +} +#[repr(C)] +#[derive(Copy, Clone)] +pub struct XReparentEvent { + pub type_: ::std::os::raw::c_int, + pub serial: ::std::os::raw::c_ulong, + pub send_event: ::std::os::raw::c_int, + pub display: *mut Display, + pub event: Window, + pub window: Window, + pub parent: Window, + pub x: ::std::os::raw::c_int, + pub y: ::std::os::raw::c_int, + pub override_redirect: ::std::os::raw::c_int, +} +#[repr(C)] +#[derive(Copy, Clone)] +pub struct XConfigureEvent { + pub type_: ::std::os::raw::c_int, + pub serial: ::std::os::raw::c_ulong, + pub send_event: ::std::os::raw::c_int, + pub display: *mut Display, + pub event: Window, + pub window: Window, + pub x: ::std::os::raw::c_int, + pub y: ::std::os::raw::c_int, + pub width: ::std::os::raw::c_int, + pub height: ::std::os::raw::c_int, + pub border_width: ::std::os::raw::c_int, + pub above: Window, + pub override_redirect: ::std::os::raw::c_int, +} +#[repr(C)] +#[derive(Copy, Clone)] +pub struct XGravityEvent { + pub type_: ::std::os::raw::c_int, + pub serial: ::std::os::raw::c_ulong, + pub send_event: ::std::os::raw::c_int, + pub display: *mut Display, + pub event: Window, + pub window: Window, + pub x: ::std::os::raw::c_int, + pub y: ::std::os::raw::c_int, +} +#[repr(C)] +#[derive(Copy, Clone)] +pub struct XResizeRequestEvent { + pub type_: ::std::os::raw::c_int, + pub serial: ::std::os::raw::c_ulong, + pub send_event: ::std::os::raw::c_int, + pub display: *mut Display, + pub window: Window, + pub width: ::std::os::raw::c_int, + pub height: ::std::os::raw::c_int, +} +#[repr(C)] +#[derive(Copy, Clone)] +pub struct XConfigureRequestEvent { + pub type_: ::std::os::raw::c_int, + pub serial: ::std::os::raw::c_ulong, + pub send_event: ::std::os::raw::c_int, + pub display: *mut Display, + pub parent: Window, + pub window: Window, + pub x: ::std::os::raw::c_int, + pub y: ::std::os::raw::c_int, + pub width: ::std::os::raw::c_int, + pub height: ::std::os::raw::c_int, + pub border_width: ::std::os::raw::c_int, + pub above: Window, + pub detail: ::std::os::raw::c_int, + pub value_mask: ::std::os::raw::c_ulong, +} +#[repr(C)] +#[derive(Copy, Clone)] +pub struct XCirculateEvent { + pub type_: ::std::os::raw::c_int, + pub serial: ::std::os::raw::c_ulong, + pub send_event: ::std::os::raw::c_int, + pub display: *mut Display, + pub event: Window, + pub window: Window, + pub place: ::std::os::raw::c_int, +} +#[repr(C)] +#[derive(Copy, Clone)] +pub struct XCirculateRequestEvent { + pub type_: ::std::os::raw::c_int, + pub serial: ::std::os::raw::c_ulong, + pub send_event: ::std::os::raw::c_int, + pub display: *mut Display, + pub parent: Window, + pub window: Window, + pub place: ::std::os::raw::c_int, +} +#[repr(C)] +#[derive(Copy, Clone)] +pub struct XPropertyEvent { + pub type_: ::std::os::raw::c_int, + pub serial: ::std::os::raw::c_ulong, + pub send_event: ::std::os::raw::c_int, + pub display: *mut Display, + pub window: Window, + pub atom: Atom, + pub time: Time, + pub state: ::std::os::raw::c_int, +} +#[repr(C)] +#[derive(Copy, Clone)] +pub struct XSelectionClearEvent { + pub type_: ::std::os::raw::c_int, + pub serial: ::std::os::raw::c_ulong, + pub send_event: ::std::os::raw::c_int, + pub display: *mut Display, + pub window: Window, + pub selection: Atom, + pub time: Time, +} +#[repr(C)] +#[derive(Copy, Clone)] +pub struct XSelectionRequestEvent { + pub type_: ::std::os::raw::c_int, + pub serial: ::std::os::raw::c_ulong, + pub send_event: ::std::os::raw::c_int, + pub display: *mut Display, + pub owner: Window, + pub requestor: Window, + pub selection: Atom, + pub target: Atom, + pub property: Atom, + pub time: Time, +} +#[repr(C)] +#[derive(Copy, Clone)] +pub struct XSelectionEvent { + pub type_: ::std::os::raw::c_int, + pub serial: ::std::os::raw::c_ulong, + pub send_event: ::std::os::raw::c_int, + pub display: *mut Display, + pub requestor: Window, + pub selection: Atom, + pub target: Atom, + pub property: Atom, + pub time: Time, +} +#[repr(C)] +#[derive(Copy, Clone)] +pub struct XColormapEvent { + pub type_: ::std::os::raw::c_int, + pub serial: ::std::os::raw::c_ulong, + pub send_event: ::std::os::raw::c_int, + pub display: *mut Display, + pub window: Window, + pub colormap: Colormap, + pub new: ::std::os::raw::c_int, + pub state: ::std::os::raw::c_int, +} +#[repr(C)] +#[derive(Copy, Clone)] +pub struct XClientMessageEvent { + pub type_: ::std::os::raw::c_int, + pub serial: ::std::os::raw::c_ulong, + pub send_event: ::std::os::raw::c_int, + pub display: *mut Display, + pub window: Window, + pub message_type: Atom, + pub format: ::std::os::raw::c_int, + pub data: XClientMessageEvent__bindgen_ty_1, +} +#[repr(C)] +#[derive(Copy, Clone)] +pub union XClientMessageEvent__bindgen_ty_1 { + pub b: [::std::os::raw::c_char; 20usize], + pub s: [::std::os::raw::c_short; 10usize], + pub l: [::std::os::raw::c_long; 5usize], + _bindgen_union_align: [u64; 5usize], +} +#[repr(C)] +#[derive(Copy, Clone)] +pub struct XMappingEvent { + pub type_: ::std::os::raw::c_int, + pub serial: ::std::os::raw::c_ulong, + pub send_event: ::std::os::raw::c_int, + pub display: *mut Display, + pub window: Window, + pub request: ::std::os::raw::c_int, + pub first_keycode: ::std::os::raw::c_int, + pub count: ::std::os::raw::c_int, +} +#[repr(C)] +#[derive(Copy, Clone)] +pub struct XErrorEvent { + pub type_: ::std::os::raw::c_int, + pub display: *mut Display, + pub resourceid: XID, + pub serial: ::std::os::raw::c_ulong, + pub error_code: ::std::os::raw::c_uchar, + pub request_code: ::std::os::raw::c_uchar, + pub minor_code: ::std::os::raw::c_uchar, +} +#[repr(C)] +#[derive(Copy, Clone)] +pub struct XAnyEvent { + pub type_: ::std::os::raw::c_int, + pub serial: ::std::os::raw::c_ulong, + pub send_event: ::std::os::raw::c_int, + pub display: *mut Display, + pub window: Window, +} +#[repr(C)] +#[derive(Copy, Clone)] +pub struct XGenericEvent { + pub type_: ::std::os::raw::c_int, + pub serial: ::std::os::raw::c_ulong, + pub send_event: ::std::os::raw::c_int, + pub display: *mut Display, + pub extension: ::std::os::raw::c_int, + pub evtype: ::std::os::raw::c_int, +} +#[repr(C)] +#[derive(Copy, Clone)] +pub struct XGenericEventCookie { + pub type_: ::std::os::raw::c_int, + pub serial: ::std::os::raw::c_ulong, + pub send_event: ::std::os::raw::c_int, + pub display: *mut Display, + pub extension: ::std::os::raw::c_int, + pub evtype: ::std::os::raw::c_int, + pub cookie: ::std::os::raw::c_uint, + pub data: *mut ::std::os::raw::c_void, +} +#[repr(C)] +#[derive(Copy, Clone)] +pub union _XEvent { + pub type_: ::std::os::raw::c_int, + pub xany: XAnyEvent, + pub xkey: XKeyEvent, + pub xbutton: XButtonEvent, + pub xmotion: XMotionEvent, + pub xcrossing: XCrossingEvent, + pub xfocus: XFocusChangeEvent, + pub xexpose: XExposeEvent, + pub xgraphicsexpose: XGraphicsExposeEvent, + pub xnoexpose: XNoExposeEvent, + pub xvisibility: XVisibilityEvent, + pub xcreatewindow: XCreateWindowEvent, + pub xdestroywindow: XDestroyWindowEvent, + pub xunmap: XUnmapEvent, + pub xmap: XMapEvent, + pub xmaprequest: XMapRequestEvent, + pub xreparent: XReparentEvent, + pub xconfigure: XConfigureEvent, + pub xgravity: XGravityEvent, + pub xresizerequest: XResizeRequestEvent, + pub xconfigurerequest: XConfigureRequestEvent, + pub xcirculate: XCirculateEvent, + pub xcirculaterequest: XCirculateRequestEvent, + pub xproperty: XPropertyEvent, + pub xselectionclear: XSelectionClearEvent, + pub xselectionrequest: XSelectionRequestEvent, + pub xselection: XSelectionEvent, + pub xcolormap: XColormapEvent, + pub xclient: XClientMessageEvent, + pub xmapping: XMappingEvent, + pub xerror: XErrorEvent, + pub xkeymap: XKeymapEvent, + pub xgeneric: XGenericEvent, + pub xcookie: XGenericEventCookie, + pub pad: [::std::os::raw::c_long; 24usize], + _bindgen_union_align: [u64; 24usize], +} +pub type XEvent = _XEvent; +extern "C" { + pub fn XOpenDisplay(arg1: *const ::std::os::raw::c_char) -> *mut Display; +} +extern "C" { + pub fn XInternAtom( + arg1: *mut Display, + arg2: *const ::std::os::raw::c_char, + arg3: ::std::os::raw::c_int, + ) -> Atom; +} +extern "C" { + pub fn XCreateGC( + arg1: *mut Display, + arg2: Drawable, + arg3: ::std::os::raw::c_ulong, + arg4: *mut XGCValues, + ) -> GC; +} +extern "C" { + pub fn XCreateSimpleWindow( + arg1: *mut Display, + arg2: Window, + arg3: ::std::os::raw::c_int, + arg4: ::std::os::raw::c_int, + arg5: ::std::os::raw::c_uint, + arg6: ::std::os::raw::c_uint, + arg7: ::std::os::raw::c_uint, + arg8: ::std::os::raw::c_ulong, + arg9: ::std::os::raw::c_ulong, + ) -> Window; +} +extern "C" { + pub fn XRootWindowOfScreen(arg1: *mut Screen) -> Window; +} +extern "C" { + pub fn XDefaultVisualOfScreen(arg1: *mut Screen) -> *mut Visual; +} +extern "C" { + pub fn XBlackPixelOfScreen(arg1: *mut Screen) -> ::std::os::raw::c_ulong; +} +extern "C" { + pub fn XDefaultScreenOfDisplay(arg1: *mut Display) -> *mut Screen; +} +extern "C" { + pub fn XScreenNumberOfScreen(arg1: *mut Screen) -> ::std::os::raw::c_int; +} +extern "C" { + pub fn XSetWMProtocols( + arg1: *mut Display, + arg2: Window, + arg3: *mut Atom, + arg4: ::std::os::raw::c_int, + ) -> ::std::os::raw::c_int; +} +extern "C" { + pub fn XClearWindow(arg1: *mut Display, arg2: Window) -> ::std::os::raw::c_int; +} +extern "C" { + pub fn XCloseDisplay(arg1: *mut Display) -> ::std::os::raw::c_int; +} +extern "C" { + pub fn XConnectionNumber(arg1: *mut Display) -> ::std::os::raw::c_int; +} +extern "C" { + pub fn XDefaultDepthOfScreen(arg1: *mut Screen) -> ::std::os::raw::c_int; +} +extern "C" { + pub fn XDestroyWindow(arg1: *mut Display, arg2: Window) -> ::std::os::raw::c_int; +} +extern "C" { + pub fn XFlush(arg1: *mut Display) -> ::std::os::raw::c_int; +} +extern "C" { + pub fn XFree(arg1: *mut ::std::os::raw::c_void) -> ::std::os::raw::c_int; +} +extern "C" { + pub fn XFreeGC(arg1: *mut Display, arg2: GC) -> ::std::os::raw::c_int; +} +extern "C" { + pub fn XMapRaised(arg1: *mut Display, arg2: Window) -> ::std::os::raw::c_int; +} +extern "C" { + pub fn XNextEvent(arg1: *mut Display, arg2: *mut XEvent) -> ::std::os::raw::c_int; +} +extern "C" { + pub fn XPending(arg1: *mut Display) -> ::std::os::raw::c_int; +} +extern "C" { + pub fn XSelectInput( + arg1: *mut Display, + arg2: Window, + arg3: ::std::os::raw::c_long, + ) -> ::std::os::raw::c_int; +} +#[repr(C)] +#[derive(Copy, Clone)] +pub struct XSizeHints { + pub flags: ::std::os::raw::c_long, + pub x: ::std::os::raw::c_int, + pub y: ::std::os::raw::c_int, + pub width: ::std::os::raw::c_int, + pub height: ::std::os::raw::c_int, + pub min_width: ::std::os::raw::c_int, + pub min_height: ::std::os::raw::c_int, + pub max_width: ::std::os::raw::c_int, + pub max_height: ::std::os::raw::c_int, + pub width_inc: ::std::os::raw::c_int, + pub height_inc: ::std::os::raw::c_int, + pub min_aspect: XSizeHints__bindgen_ty_1, + pub max_aspect: XSizeHints__bindgen_ty_1, + pub base_width: ::std::os::raw::c_int, + pub base_height: ::std::os::raw::c_int, + pub win_gravity: ::std::os::raw::c_int, +} +#[repr(C)] +#[derive(Copy, Clone)] +pub struct XSizeHints__bindgen_ty_1 { + pub x: ::std::os::raw::c_int, + pub y: ::std::os::raw::c_int, +} +extern "C" { + pub fn XDestroyImage(ximage: *mut XImage) -> ::std::os::raw::c_int; +} +#[repr(C)] +#[derive(Copy, Clone)] +pub struct XVisualInfo { + pub visual: *mut Visual, + pub visualid: VisualID, + pub screen: ::std::os::raw::c_int, + pub depth: ::std::os::raw::c_int, + pub class: ::std::os::raw::c_int, + pub red_mask: ::std::os::raw::c_ulong, + pub green_mask: ::std::os::raw::c_ulong, + pub blue_mask: ::std::os::raw::c_ulong, + pub colormap_size: ::std::os::raw::c_int, + pub bits_per_rgb: ::std::os::raw::c_int, +} +extern "C" { + pub fn XAllocSizeHints() -> *mut XSizeHints; +} +extern "C" { + pub fn XGetVisualInfo( + arg1: *mut Display, + arg2: ::std::os::raw::c_long, + arg3: *mut XVisualInfo, + arg4: *mut ::std::os::raw::c_int, + ) -> *mut XVisualInfo; +} +extern "C" { + pub fn XSetWMNormalHints(arg1: *mut Display, arg2: Window, arg3: *mut XSizeHints); +} +pub type ShmSeg = ::std::os::raw::c_ulong; +#[repr(C)] +#[derive(Copy, Clone)] +pub struct XShmCompletionEvent { + pub type_: ::std::os::raw::c_int, + pub serial: ::std::os::raw::c_ulong, + pub send_event: ::std::os::raw::c_int, + pub display: *mut Display, + pub drawable: Drawable, + pub major_code: ::std::os::raw::c_int, + pub minor_code: ::std::os::raw::c_int, + pub shmseg: ShmSeg, + pub offset: ::std::os::raw::c_ulong, +} +#[repr(C)] +#[derive(Copy, Clone)] +pub struct XShmSegmentInfo { + pub shmseg: ShmSeg, + pub shmid: ::std::os::raw::c_int, + pub shmaddr: *mut ::std::os::raw::c_char, + pub readOnly: ::std::os::raw::c_int, +} +extern "C" { + pub fn XShmQueryExtension(arg1: *mut Display) -> ::std::os::raw::c_int; +} +extern "C" { + pub fn XShmGetEventBase(arg1: *mut Display) -> ::std::os::raw::c_int; +} +extern "C" { + pub fn XShmAttach(arg1: *mut Display, arg2: *mut XShmSegmentInfo) -> ::std::os::raw::c_int; +} +extern "C" { + pub fn XShmDetach(arg1: *mut Display, arg2: *mut XShmSegmentInfo) -> ::std::os::raw::c_int; +} +extern "C" { + pub fn XShmPutImage( + arg1: *mut Display, + arg2: Drawable, + arg3: GC, + arg4: *mut XImage, + arg5: ::std::os::raw::c_int, + arg6: ::std::os::raw::c_int, + arg7: ::std::os::raw::c_int, + arg8: ::std::os::raw::c_int, + arg9: ::std::os::raw::c_uint, + arg10: ::std::os::raw::c_uint, + arg11: ::std::os::raw::c_int, + ) -> ::std::os::raw::c_int; +} +extern "C" { + pub fn XShmCreateImage( + arg1: *mut Display, + arg2: *mut Visual, + arg3: ::std::os::raw::c_uint, + arg4: ::std::os::raw::c_int, + arg5: *mut ::std::os::raw::c_char, + arg6: *mut XShmSegmentInfo, + arg7: ::std::os::raw::c_uint, + arg8: ::std::os::raw::c_uint, + ) -> *mut XImage; +} diff --git a/gpu_display/src/generated/xlib_generator.sh b/gpu_display/src/generated/xlib_generator.sh new file mode 100755 index 0000000..b915b33 --- /dev/null +++ b/gpu_display/src/generated/xlib_generator.sh @@ -0,0 +1,76 @@ +#!/bin/bash +# 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. + +cd "${0%/*}" + +cat >xlib.rs <<EOF +// 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. + +//! Generated using ./xlib_generator.sh + +#[link(name = "X11")] +extern "C" {} + +#[link(name = "Xext")] +extern "C" {} + +EOF + +bindgen --no-layout-tests --no-derive-debug \ + --whitelist-function XAllocSizeHints \ + --whitelist-function XBlackPixelOfScreen \ + --whitelist-function XClearWindow \ + --whitelist-function XCloseDisplay \ + --whitelist-function XConnectionNumber \ + --whitelist-function XCreateGC \ + --whitelist-function XCreateSimpleWindow \ + --whitelist-function XDefaultDepthOfScreen \ + --whitelist-function XDefaultScreenOfDisplay \ + --whitelist-function XDefaultVisualOfScreen \ + --whitelist-function XDestroyImage \ + --whitelist-function XDestroyWindow \ + --whitelist-function XFlush \ + --whitelist-function XFree \ + --whitelist-function XFreeGC \ + --whitelist-function XGetVisualInfo \ + --whitelist-function XInternAtom \ + --whitelist-function XMapRaised \ + --whitelist-function XNextEvent \ + --whitelist-function XOpenDisplay \ + --whitelist-function XPending \ + --whitelist-function XRootWindowOfScreen \ + --whitelist-function XScreenNumberOfScreen \ + --whitelist-function XSelectInput \ + --whitelist-function XSetWMNormalHints \ + --whitelist-function XSetWMProtocols \ + --whitelist-function XShmAttach \ + --whitelist-function XShmCreateImage \ + --whitelist-function XShmDetach \ + --whitelist-function XShmGetEventBase \ + --whitelist-function XShmPutImage \ + --whitelist-function XShmQueryExtension \ + --whitelist-var ClientMessage \ + --whitelist-var Expose \ + --whitelist-var ExposureMask \ + --whitelist-var PMaxSize \ + --whitelist-var PMinSize \ + --whitelist-var ShmCompletion \ + --whitelist-var VisualBlueMaskMask \ + --whitelist-var VisualDepthMask \ + --whitelist-var VisualGreenMaskMask \ + --whitelist-var VisualRedMaskMask \ + --whitelist-var VisualScreenMask \ + --whitelist-var ZPixmap \ + --whitelist-type Display \ + --whitelist-type GC \ + --whitelist-type Screen \ + --whitelist-type XShmCompletionEvent \ + --whitelist-type ShmSeg \ + --whitelist-type Visual \ + --whitelist-type Window \ + --whitelist-type XVisualInfo \ + xlib_wrapper.h >>xlib.rs diff --git a/gpu_display/src/generated/xlib_wrapper.h b/gpu_display/src/generated/xlib_wrapper.h new file mode 100644 index 0000000..2084799 --- /dev/null +++ b/gpu_display/src/generated/xlib_wrapper.h @@ -0,0 +1,4 @@ +#define XUTIL_DEFINE_FUNCTIONS +#include <X11/Xlib.h> +#include <X11/Xutil.h> +#include <X11/extensions/XShm.h> diff --git a/gpu_display/src/gpu_display_wl.rs b/gpu_display/src/gpu_display_wl.rs new file mode 100644 index 0000000..a079d87 --- /dev/null +++ b/gpu_display/src/gpu_display_wl.rs @@ -0,0 +1,347 @@ +// 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. + +//! Crate for displaying simple surfaces and GPU buffers over wayland. + +extern crate data_model; +extern crate sys_util; + +#[path = "dwl.rs"] +mod dwl; + +use dwl::*; + +use crate::{DisplayT, GpuDisplayError, GpuDisplayFramebuffer}; + +use std::cell::Cell; +use std::collections::HashMap; +use std::ffi::{CStr, CString}; +use std::os::unix::io::{AsRawFd, RawFd}; +use std::path::Path; +use std::ptr::{null, null_mut}; + +use data_model::VolatileMemory; +use sys_util::{round_up_to_page_size, MemoryMapping, SharedMemory}; + +const BUFFER_COUNT: usize = 2; +const BYTES_PER_PIXEL: u32 = 4; + +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 Surface { + surface: DwlSurface, + row_size: u32, + buffer_size: usize, + buffer_index: Cell<usize>, + buffer_mem: MemoryMapping, +} + +impl Surface { + 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 DisplayWl { + ctx: DwlContext, + dmabufs: HashMap<u32, DwlDmabuf>, + dmabuf_next_id: u32, + surfaces: HashMap<u32, Surface>, + surface_next_id: u32, +} + +impl DisplayWl { + /// Opens a fresh connection to the compositor. + pub fn new(wayland_path: Option<&Path>) -> Result<DisplayWl, 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 cstr_path = match wayland_path.map(|p| p.as_os_str().to_str()) { + Some(Some(s)) => match CString::new(s) { + Ok(cstr) => Some(cstr), + Err(_) => return Err(GpuDisplayError::InvalidPath), + }, + Some(None) => return Err(GpuDisplayError::InvalidPath), + None => None, + }; + let setup_success = + unsafe { dwl_context_setup(ctx.0, cstr_path.map(|s| s.as_ptr()).unwrap_or(null())) }; + if !setup_success { + return Err(GpuDisplayError::Connect); + } + + Ok(DisplayWl { + 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<&Surface> { + self.surfaces.get(&surface_id) + } +} + +impl DisplayT for DisplayWl { + 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) + } + + fn release_import(&mut self, import_id: u32) { + self.dmabufs.remove(&import_id); + } + + fn dispatch_events(&mut self) { + // Safe given that the context pointer is valid. + unsafe { + dwl_context_dispatch(self.ctx()); + } + } + + 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, + Surface { + surface, + row_size, + buffer_size: fb_size as usize, + buffer_index: Cell::new(0), + buffer_mem, + }, + ); + + self.surface_next_id += 1; + Ok(next_id) + } + + fn release_surface(&mut self, surface_id: u32) { + self.surfaces.remove(&surface_id); + } + + fn framebuffer(&mut self, surface_id: u32) -> Option<GpuDisplayFramebuffer> { + let surface = self.get_surface(surface_id)?; + let buffer_index = (surface.buffer_index.get() + 1) % BUFFER_COUNT; + let framebuffer = surface + .buffer_mem + .get_slice( + (buffer_index * surface.buffer_size) as u64, + surface.buffer_size as u64, + ) + .ok()?; + Some(GpuDisplayFramebuffer::new( + framebuffer, + surface.row_size, + BYTES_PER_PIXEL, + )) + } + + fn commit(&mut 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), + } + } + + 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 + } + } + } + + fn flip(&mut 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), + } + } + + fn flip_to(&mut 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), + } + } + + 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, + } + } + + fn set_position(&mut 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 AsRawFd for DisplayWl { + fn as_raw_fd(&self) -> RawFd { + // Safe given that the context pointer is valid. + unsafe { dwl_context_fd(self.ctx.0) } + } +} 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() + } +} diff --git a/gpu_display/src/lib.rs b/gpu_display/src/lib.rs index 11a6703..1bf9578 100644 --- a/gpu_display/src/lib.rs +++ b/gpu_display/src/lib.rs @@ -4,23 +4,16 @@ //! Crate for displaying simple surfaces and GPU buffers over wayland. -mod dwl; - -use std::cell::Cell; -use std::collections::HashMap; -use std::ffi::{CStr, CString}; use std::fmt::{self, Display}; use std::os::unix::io::{AsRawFd, RawFd}; use std::path::Path; -use std::ptr::null_mut; - -use data_model::{VolatileMemory, VolatileSlice}; -use sys_util::{round_up_to_page_size, Error as SysError, MemoryMapping, SharedMemory}; -use crate::dwl::*; +use data_model::VolatileSlice; +use sys_util::Error as SysError; -const BUFFER_COUNT: usize = 2; -const BYTES_PER_PIXEL: u32 = 4; +mod gpu_display_wl; +#[cfg(feature = "x")] +mod gpu_display_x; /// An error generated by `GpuDisplay`. #[derive(Debug)] @@ -39,8 +32,12 @@ pub enum GpuDisplayError { FailedImport, /// The surface ID is invalid. InvalidSurfaceId, + /// A required feature was missing. + RequiredFeature(&'static str), /// The path is invalid. InvalidPath, + /// The method is unsupported by the implementation. + Unsupported, } impl Display for GpuDisplayError { @@ -51,65 +48,110 @@ impl Display for GpuDisplayError { Allocate => write!(f, "internal allocation failed"), Connect => write!(f, "failed to connect to compositor"), CreateShm(e) => write!(f, "failed to create shared memory: {}", e), - SetSize(e) => write!(f, "failed to set size of shared memory: {}", e), CreateSurface => write!(f, "failed to crate surface on the compositor"), FailedImport => write!(f, "failed to import a buffer to the compositor"), - InvalidSurfaceId => write!(f, "invalid surface ID"), InvalidPath => write!(f, "invalid path"), + InvalidSurfaceId => write!(f, "invalid surface ID"), + RequiredFeature(feature) => write!(f, "required feature was missing: {}", feature), + SetSize(e) => write!(f, "failed to set size of shared memory: {}", e), + Unsupported => write!(f, "unsupported by the implementation"), } } } -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); - } - } - } +#[derive(Clone)] +pub struct GpuDisplayFramebuffer<'a> { + framebuffer: VolatileSlice<'a>, + slice: VolatileSlice<'a>, + stride: u32, + bytes_per_pixel: u32, } -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); - } +impl<'a> GpuDisplayFramebuffer<'a> { + fn new( + framebuffer: VolatileSlice<'a>, + stride: u32, + bytes_per_pixel: u32, + ) -> GpuDisplayFramebuffer { + GpuDisplayFramebuffer { + framebuffer, + slice: framebuffer, + stride, + bytes_per_pixel, } } -} -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); - } - } + fn sub_region( + &self, + x: u32, + y: u32, + width: u32, + height: u32, + ) -> Option<GpuDisplayFramebuffer<'a>> { + let x_byte_offset = x.checked_mul(self.bytes_per_pixel)?; + let y_byte_offset = y.checked_mul(self.stride)?; + let byte_offset = x_byte_offset.checked_add(y_byte_offset)?; + + let width_bytes = width.checked_mul(self.bytes_per_pixel)?; + let count = height + .checked_mul(self.stride)? + .checked_sub(self.stride)? + .checked_add(width_bytes)?; + let slice = self + .framebuffer + .sub_slice(byte_offset as u64, count as u64) + .unwrap(); + + Some(GpuDisplayFramebuffer { slice, ..*self }) + } + + pub fn as_volatile_slice(&self) -> VolatileSlice<'a> { + self.slice } -} -struct GpuDisplaySurface { - surface: DwlSurface, - buffer_size: usize, - buffer_index: Cell<usize>, - buffer_mem: MemoryMapping, + pub fn stride(&self) -> u32 { + self.stride + } } -impl GpuDisplaySurface { - fn surface(&self) -> *mut dwl_surface { - self.surface.0 +trait DisplayT: AsRawFd { + fn import_dmabuf( + &mut self, + fd: RawFd, + offset: u32, + stride: u32, + modifiers: u64, + width: u32, + height: u32, + fourcc: u32, + ) -> Result<u32, GpuDisplayError>; + fn release_import(&mut self, import_id: u32); + fn dispatch_events(&mut self); + fn create_surface( + &mut self, + parent_surface_id: Option<u32>, + width: u32, + height: u32, + ) -> Result<u32, GpuDisplayError>; + fn release_surface(&mut self, surface_id: u32); + fn framebuffer(&mut self, surface_id: u32) -> Option<GpuDisplayFramebuffer>; + fn framebuffer_region( + &mut self, + surface_id: u32, + x: u32, + y: u32, + width: u32, + height: u32, + ) -> Option<GpuDisplayFramebuffer> { + let framebuffer = self.framebuffer(surface_id)?; + framebuffer.sub_region(x, y, width, height) } + fn commit(&mut self, surface_id: u32); + fn next_buffer_in_use(&self, surface_id: u32) -> bool; + fn flip(&mut self, surface_id: u32); + fn flip_to(&mut self, surface_id: u32, import_id: u32); + fn close_requested(&self, surface_id: u32) -> bool; + fn set_position(&mut self, surface_id: u32, x: u32, y: u32); } /// A connection to the compositor and associated collection of state. @@ -117,51 +159,35 @@ impl GpuDisplaySurface { /// 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, + inner: Box<DisplayT>, } impl GpuDisplay { - /// Opens a fresh connection to the compositor. - pub fn new<P: AsRef<Path>>(wayland_path: P) -> 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 cstr_path = match wayland_path.as_ref().as_os_str().to_str() { - Some(str) => match CString::new(str) { - Ok(cstr) => cstr, - Err(_) => return Err(GpuDisplayError::InvalidPath), - }, - None => return Err(GpuDisplayError::InvalidPath), - }; - let setup_success = unsafe { dwl_context_setup(ctx.0, cstr_path.as_ptr()) }; - if !setup_success { - return Err(GpuDisplayError::Connect); + pub fn open_x<S: AsRef<str>>(display_name: Option<S>) -> Result<GpuDisplay, GpuDisplayError> { + let _ = display_name; + #[cfg(feature = "x")] + { + let display = match display_name { + Some(s) => gpu_display_x::DisplayX::open_display(Some(s.as_ref()))?, + None => gpu_display_x::DisplayX::open_display(None)?, + }; + let inner = Box::new(display); + Ok(GpuDisplay { inner }) } - - 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 + #[cfg(not(feature = "x"))] + Err(GpuDisplayError::Unsupported) } - fn get_surface(&self, surface_id: u32) -> Option<&GpuDisplaySurface> { - self.surfaces.get(&surface_id) + /// Opens a fresh connection to the compositor. + pub fn open_wayland<P: AsRef<Path>>( + wayland_path: Option<P>, + ) -> Result<GpuDisplay, GpuDisplayError> { + let display = match wayland_path { + Some(s) => gpu_display_wl::DisplayWl::new(Some(s.as_ref()))?, + None => gpu_display_wl::DisplayWl::new(None)?, + }; + let inner = Box::new(display); + Ok(GpuDisplay { inner }) } /// Imports a dmabuf to the compositor for use as a surface buffer and returns a handle to it. @@ -175,43 +201,19 @@ impl GpuDisplay { 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) + self.inner + .import_dmabuf(fd, offset, stride, modifiers, width, height, fourcc) } /// Releases a previously imported dmabuf identified by the given handle. pub fn release_import(&mut self, import_id: u32) { - self.dmabufs.remove(&import_id); + self.inner.release_import(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()); - } + self.inner.dispatch_events() } /// Creates a surface on the the compositor as either a top level window, or child of another @@ -222,88 +224,35 @@ impl GpuDisplay { 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) + self.inner.create_surface(parent_surface_id, width, height) } /// Releases a previously created surface identified by the given handle. pub fn release_surface(&mut self, surface_id: u32) { - self.surfaces.remove(&surface_id); + self.inner.release_surface(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() + pub fn framebuffer(&mut self, surface_id: u32) -> Option<GpuDisplayFramebuffer> { + self.inner.framebuffer(surface_id) + } + + /// Gets a reference to an unused framebuffer for the identified surface. + pub fn framebuffer_region( + &mut self, + surface_id: u32, + x: u32, + y: u32, + width: u32, + height: u32, + ) -> Option<GpuDisplayFramebuffer> { + self.inner + .framebuffer_region(surface_id, x, y, width, height) } /// 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), - } + pub fn commit(&mut self, surface_id: u32) { + self.inner.commit(surface_id) } /// Returns true if the next buffer in the buffer queue for the given surface is currently in @@ -312,88 +261,37 @@ impl GpuDisplay { /// 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 - } - } + self.inner.next_buffer_in_use(surface_id) } /// 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), - } + pub fn flip(&mut self, surface_id: u32) { + self.inner.flip(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), - } + pub fn flip_to(&mut self, surface_id: u32, import_id: u32) { + self.inner.flip_to(surface_id, import_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, - } + self.inner.close_requested(surface_id) } /// 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) } + pub fn set_position(&mut self, surface_id: u32, x: u32, y: u32) { + self.inner.set_position(surface_id, x, y) } } 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) } + self.inner.as_raw_fd() } } diff --git a/gpu_renderer/src/lib.rs b/gpu_renderer/src/lib.rs index 270dc93..5311c82 100644 --- a/gpu_renderer/src/lib.rs +++ b/gpu_renderer/src/lib.rs @@ -32,7 +32,9 @@ use crate::generated::epoxy_egl::{ EGL_DMA_BUF_PLANE0_PITCH_EXT, EGL_GL_TEXTURE_2D_KHR, EGL_HEIGHT, EGL_LINUX_DMA_BUF_EXT, EGL_LINUX_DRM_FOURCC_EXT, EGL_NONE, EGL_OPENGL_ES_API, EGL_SURFACE_TYPE, EGL_WIDTH, }; -use crate::generated::p_defines::{PIPE_BIND_SAMPLER_VIEW, PIPE_TEXTURE_1D, PIPE_TEXTURE_2D}; +use crate::generated::p_defines::{ + PIPE_BIND_RENDER_TARGET, PIPE_BIND_SAMPLER_VIEW, PIPE_TEXTURE_1D, PIPE_TEXTURE_2D, +}; use crate::generated::p_format::PIPE_FORMAT_B8G8R8X8_UNORM; use crate::generated::virglrenderer::*; @@ -78,6 +80,10 @@ pub enum Error { InvalidIovec, /// A command size was submitted that was invalid. InvalidCommandSize(usize), + /// The image export request is not supported without EGL. + ExportUnsupported, + /// The image import request is not supported without EGL. + ImportUnsupported, } impl Display for Error { @@ -98,6 +104,8 @@ impl Display for Error { ExportedResourceDmabuf => write!(f, "failed to export dmabuf from EGLImageKHR"), InvalidIovec => write!(f, "an iovec is outside of guest memory's range"), InvalidCommandSize(s) => write!(f, "command buffer submitted with invalid size: {}", s), + ExportUnsupported => write!(f, "can not export images without EGL"), + ImportUnsupported => write!(f, "can not import images without EGL"), } } } @@ -308,11 +316,139 @@ impl Deref for EGLFunctions { } } +fn init_egl() -> Result<(EGLDisplay, EGLFunctions)> { + let egl_funcs = EGLFunctions::new()?; + + // Safe because only valid callbacks are given and only one thread can execute this + // function. + unsafe { + (egl_funcs.DebugMessageControlKHR)(Some(error_callback), null()); + } + + // Trivially safe. + let display = unsafe { (egl_funcs.GetDisplay)(null_mut()) }; + if display.is_null() { + return Err(Error::EGLGetDisplay); + } + + // Safe because only a valid display is given. + let ret = unsafe { (egl_funcs.Initialize)(display, null_mut(), null_mut()) }; + if ret == 0 { + return Err(Error::EGLInitialize); + } + + let config_attribs = [EGL_SURFACE_TYPE as i32, -1, EGL_NONE as i32]; + let mut egl_config: *mut c_void = null_mut(); + let mut num_configs = 0; + // Safe because only a valid, initialized display is used, along with validly sized + // pointers to stack variables. + let ret = unsafe { + (egl_funcs.ChooseConfig)( + display, + config_attribs.as_ptr(), + &mut egl_config, + 1, + &mut num_configs, /* unused but can't be null */ + ) + }; + if ret == 0 { + return Err(Error::EGLChooseConfig); + } + + // Safe because EGL was properly initialized before here.. + let ret = unsafe { (egl_funcs.BindAPI)(EGL_OPENGL_ES_API) }; + if ret == 0 { + return Err(Error::EGLBindAPI); + } + + let context_attribs = [EGL_CONTEXT_CLIENT_VERSION as i32, 3, EGL_NONE as i32]; + // Safe because a valid display, config, and config_attribs pointer are given. + let ctx = unsafe { + (egl_funcs.CreateContext)(display, egl_config, null_mut(), context_attribs.as_ptr()) + }; + if ctx.is_null() { + return Err(Error::EGLCreateContext); + } + + // Safe because a valid display and context is used, and the two null surfaces are not + // used. + let ret = unsafe { (egl_funcs.MakeCurrent)(display, null_mut(), null_mut(), ctx) }; + if ret == 0 { + return Err(Error::EGLMakeCurrent); + } + + Ok((display, egl_funcs)) +} + +#[derive(Copy, Clone)] +pub struct RendererFlags(u32); + +impl Default for RendererFlags { + fn default() -> RendererFlags { + RendererFlags::new() + .use_egl(true) + .use_surfaceless(true) + .use_gles(true) + } +} + +impl RendererFlags { + pub fn new() -> RendererFlags { + RendererFlags(0) + } + + fn set_flag(self, bitmask: u32, set: bool) -> RendererFlags { + if set { + RendererFlags(self.0 | bitmask) + } else { + RendererFlags(self.0 & (!bitmask)) + } + } + + pub fn uses_egl(self) -> bool { + (self.0 & VIRGL_RENDERER_USE_EGL) != 0 + } + + pub fn use_egl(self, v: bool) -> RendererFlags { + self.set_flag(VIRGL_RENDERER_USE_EGL, v) + } + + pub fn uses_glx(self) -> bool { + (self.0 & VIRGL_RENDERER_USE_GLX) != 0 + } + + pub fn use_glx(self, v: bool) -> RendererFlags { + self.set_flag(VIRGL_RENDERER_USE_GLX, v) + } + + pub fn uses_surfaceless(self) -> bool { + (self.0 & VIRGL_RENDERER_USE_SURFACELESS) != 0 + } + + pub fn use_surfaceless(self, v: bool) -> RendererFlags { + self.set_flag(VIRGL_RENDERER_USE_SURFACELESS, v) + } + + pub fn uses_gles(self) -> bool { + (self.0 & VIRGL_RENDERER_USE_GLES) != 0 + } + + pub fn use_gles(self, v: bool) -> RendererFlags { + self.set_flag(VIRGL_RENDERER_USE_GLES, v) + } +} + +impl From<RendererFlags> for i32 { + fn from(flags: RendererFlags) -> i32 { + flags.0 as i32 + } +} + /// The global renderer handle used to query capability sets, and create resources and contexts. pub struct Renderer { no_sync_send: PhantomData<*mut ()>, - egl_funcs: EGLFunctions, - display: EGLDisplay, + egl_funcs: Option<EGLFunctions>, + display: Option<EGLDisplay>, fence_state: Rc<RefCell<FenceState>>, } @@ -320,7 +456,7 @@ impl Renderer { /// Initializes the renderer and returns a handle to it. /// /// This may only be called once per process. Calls after the first will return an error. - pub fn init() -> Result<Renderer> { + pub fn init(flags: RendererFlags) -> Result<Renderer> { // virglrenderer is a global state backed library that uses thread bound OpenGL contexts. // Initialize it only once and use the non-send/non-sync Renderer struct to keep things tied // to whichever thread called this function first. @@ -329,42 +465,13 @@ impl Renderer { return Err(Error::AlreadyInitialized); } - let egl_funcs = EGLFunctions::new()?; - - // Safe because only valid callbacks are given and only one thread can execute this - // function. - unsafe { - (egl_funcs.DebugMessageControlKHR)(Some(error_callback), null()); - } - - // Trivially safe. - let display = unsafe { (egl_funcs.GetDisplay)(null_mut()) }; - if display.is_null() { - return Err(Error::EGLGetDisplay); - } - - // Safe because only a valid display is given. - let ret = unsafe { (egl_funcs.Initialize)(display, null_mut(), null_mut()) }; - if ret == 0 { - return Err(Error::EGLInitialize); - } - - let config_attribs = [EGL_SURFACE_TYPE as i32, -1, EGL_NONE as i32]; - let mut egl_config: *mut c_void = null_mut(); - let mut num_configs = 0; - // Safe because only a valid, initialized display is used, along with validly sized - // pointers to stack variables. - let ret = unsafe { - (egl_funcs.ChooseConfig)( - display, - config_attribs.as_ptr(), - &mut egl_config, - 1, - &mut num_configs, /* unused but can't be null */ - ) - }; - if ret == 0 { - return Err(Error::EGLChooseConfig); + let mut display = None; + let mut egl_funcs = None; + if flags.uses_egl() { + if let Ok((d, f)) = init_egl() { + display = Some(d); + egl_funcs = Some(f); + }; } // Cookie is intentionally never freed because virglrenderer never gets uninitialized. @@ -378,35 +485,12 @@ impl Renderer { fence_state: Rc::clone(&fence_state), })); - // Safe because EGL was properly initialized before here.. - let ret = unsafe { (egl_funcs.BindAPI)(EGL_OPENGL_ES_API) }; - if ret == 0 { - return Err(Error::EGLBindAPI); - } - - let context_attribs = [EGL_CONTEXT_CLIENT_VERSION as i32, 3, EGL_NONE as i32]; - // Safe because a valid display, config, and config_attribs pointer are given. - let ctx = unsafe { - (egl_funcs.CreateContext)(display, egl_config, null_mut(), context_attribs.as_ptr()) - }; - if ctx.is_null() { - return Err(Error::EGLCreateContext); - } - - // Safe because a valid display and context is used, and the two null surfaces are not - // used. - let ret = unsafe { (egl_funcs.MakeCurrent)(display, null_mut(), null_mut(), ctx) }; - if ret == 0 { - return Err(Error::EGLMakeCurrent); - } - // Safe because a valid cookie and set of callbacks is used and the result is checked for // error. let ret = unsafe { virgl_renderer_init( cookie as *mut c_void, - (VIRGL_RENDERER_USE_EGL | VIRGL_RENDERER_USE_SURFACELESS | VIRGL_RENDERER_USE_GLES) - as i32, + flags.into(), transmute(VIRGL_RENDERER_CALLBACKS), ) }; @@ -485,6 +569,30 @@ impl Renderer { }) } + /// Helper that creates a simple 2 dimensional resource with basic metadata and usable for + /// display. + pub fn create_resource_2d( + &self, + id: u32, + width: u32, + height: u32, + format: u32, + ) -> Result<Resource> { + self.create_resource(virgl_renderer_resource_create_args { + handle: id, + target: PIPE_TEXTURE_2D, + format, + width, + height, + depth: 1, + array_size: 1, + last_level: 0, + nr_samples: 0, + bind: PIPE_BIND_RENDER_TARGET, + flags: 0, + }) + } + /// Imports a resource from an EGLImage. pub fn import_resource( &self, @@ -519,7 +627,8 @@ impl Renderer { }) } - /// Helper that creates a simple 2 dimensional resource with basic metadata. + /// Helper that creates a simple 2 dimensional resource with basic metadata and usable as a + /// texture. pub fn create_tex_2d(&self, id: u32, width: u32, height: u32) -> Result<Resource> { self.create_resource(virgl_renderer_resource_create_args { handle: id, @@ -546,6 +655,11 @@ impl Renderer { offset: u32, stride: u32, ) -> Result<Image> { + let (egl_dpy, egl_funcs) = match (self.display, self.egl_funcs.clone()) { + (Some(d), Some(f)) => (d, f), + _ => return Err(Error::ImportUnsupported), + }; + let mut attrs = [ EGL_WIDTH as EGLint, width as EGLint, @@ -563,8 +677,8 @@ impl Renderer { ]; let image = unsafe { - (self.egl_funcs.CreateImageKHR)( - self.display, + (egl_funcs.CreateImageKHR)( + egl_dpy, 0 as EGLContext, EGL_LINUX_DMA_BUF_EXT, null_mut() as EGLClientBuffer, @@ -577,8 +691,8 @@ impl Renderer { } Ok(Image { - egl_funcs: self.egl_funcs.clone(), - egl_dpy: self.display, + egl_funcs, + egl_dpy, image, }) } @@ -692,7 +806,7 @@ pub struct Resource { id: u32, backing_iovecs: Vec<VirglVec>, backing_mem: Option<GuestMemory>, - egl_funcs: EGLFunctions, + egl_funcs: Option<EGLFunctions>, no_sync_send: PhantomData<*mut ()>, } @@ -712,6 +826,11 @@ impl Resource { /// Performs an export of this resource so that it may be imported by other processes. pub fn export(&self) -> Result<ExportedResource> { + let egl_funcs = match self.egl_funcs.as_ref() { + Some(f) => f, + None => return Err(Error::ExportUnsupported), + }; + let res_info = self.get_info()?; let mut fourcc = 0; let mut modifiers = 0; @@ -724,13 +843,13 @@ impl Resource { } // These are trivially safe and always return successfully because we bind the context in // the previous line. - let egl_dpy: EGLDisplay = unsafe { (self.egl_funcs.GetCurrentDisplay)() }; - let egl_ctx: EGLContext = unsafe { (self.egl_funcs.GetCurrentContext)() }; + let egl_dpy: EGLDisplay = unsafe { (egl_funcs.GetCurrentDisplay)() }; + let egl_ctx: EGLContext = unsafe { (egl_funcs.GetCurrentContext)() }; // Safe because a valid display, context, and texture ID are given. The attribute list is // not needed. The result is checked to ensure the returned image is valid. let image = unsafe { - (self.egl_funcs.CreateImageKHR)( + (egl_funcs.CreateImageKHR)( egl_dpy, egl_ctx, EGL_GL_TEXTURE_2D_KHR, @@ -746,26 +865,21 @@ impl Resource { // Safe because the display and image are valid and each function call is checked for // success. The returned image parameters are stored in stack variables of the correct type. let export_success = unsafe { - (self.egl_funcs.ExportDMABUFImageQueryMESA)( + (egl_funcs.ExportDMABUFImageQueryMESA)( egl_dpy, image, &mut fourcc, null_mut(), &mut modifiers, ) != 0 - && (self.egl_funcs.ExportDRMImageMESA)( - egl_dpy, - image, - &mut fd, - &mut stride, - &mut offset, - ) != 0 + && (egl_funcs.ExportDRMImageMESA)(egl_dpy, image, &mut fd, &mut stride, &mut offset) + != 0 }; // Safe because we checked that the image was valid and nobody else owns it. The image does // not need to be around for the dmabuf to be valid. unsafe { - (self.egl_funcs.DestroyImageKHR)(egl_dpy, image); + (egl_funcs.DestroyImageKHR)(egl_dpy, image); } if !export_success || fd < 0 { @@ -989,7 +1103,8 @@ mod tests { #[ignore] // Make sure a simple buffer clear works by using a command stream. fn simple_clear() { - let render = Renderer::init().expect("failed to initialize virglrenderer"); + let render = + Renderer::init(RendererFlags::default()).expect("failed to initialize virglrenderer"); let mut ctx = render.create_context(1).expect("failed to create context"); // Create a 50x50 texture with id=2. |