summary refs log tree commit diff
diff options
context:
space:
mode:
authorZach Reizner <zachr@google.com>2019-04-16 15:09:20 -0700
committerCommit Bot <commit-bot@chromium.org>2019-07-25 22:15:48 +0000
commitf5285c647acacb4f25ef8cf9334254b976e71686 (patch)
treefcc238ec97736727a9c18b3b9de29be3dce3983e
parentb2110bef59d72529d99c722df9b3e9a1d705e6f4 (diff)
downloadcrosvm-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.toml1
-rw-r--r--devices/Cargo.toml1
-rw-r--r--devices/src/virtio/gpu/backend.rs145
-rw-r--r--devices/src/virtio/gpu/mod.rs198
-rw-r--r--gpu_buffer/src/lib.rs14
-rw-r--r--gpu_display/Cargo.toml3
-rw-r--r--gpu_display/examples/simple.rs4
-rw-r--r--gpu_display/examples/simple_open.rs25
-rw-r--r--gpu_display/src/generated/xlib.rs892
-rwxr-xr-xgpu_display/src/generated/xlib_generator.sh76
-rw-r--r--gpu_display/src/generated/xlib_wrapper.h4
-rw-r--r--gpu_display/src/gpu_display_wl.rs347
-rw-r--r--gpu_display/src/gpu_display_x.rs647
-rw-r--r--gpu_display/src/lib.rs404
-rw-r--r--gpu_renderer/src/lib.rs281
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.