summary refs log tree commit diff
diff options
context:
space:
mode:
authorJason Macnak <natsu@google.com>2020-01-10 12:45:36 -0800
committerCommit Bot <commit-bot@chromium.org>2020-02-03 11:14:22 +0000
commit327fc2454cdfe36fb612c73c9994435b8602c81a (patch)
treea1fc9a44068612168c1db7884189714d9e4f78ee
parentbc499ec2780510083f001bef569449808ad55850 (diff)
downloadcrosvm-327fc2454cdfe36fb612c73c9994435b8602c81a.tar
crosvm-327fc2454cdfe36fb612c73c9994435b8602c81a.tar.gz
crosvm-327fc2454cdfe36fb612c73c9994435b8602c81a.tar.bz2
crosvm-327fc2454cdfe36fb612c73c9994435b8602c81a.tar.lz
crosvm-327fc2454cdfe36fb612c73c9994435b8602c81a.tar.xz
crosvm-327fc2454cdfe36fb612c73c9994435b8602c81a.tar.zst
crosvm-327fc2454cdfe36fb612c73c9994435b8602c81a.zip
virtio-gpu: implement 2D GPU Backend
... which does not require virglrenderer (or any renderer).

This will allow the Cuttlefish team to use minigbm as its gralloc
implementation when both hardware acceleration is available and
unavailable.

Adds a GPU `Backend` trait with all of the existing methods of the
current backend and converts the existing `Backend` into
`Virtio3DBackend` which implements the new trait.

Adds a `Virtio2DBackend` which creates resources with byte vectors on
the host and implements transfers via the old code from
gpu_buffer/src/lib.rs.

Adds a runtime flag to select between 2D and 3D mode with 3D mode as
the default.

Moves the process_resource_bridge() function to the `Frontend` and
instead expose a export_resource() function on the `Backend` to avoid
some code duplication.

BUG=b:123764798
TEST=build + launch cuttlefish w/ 2D mode (minigbm + custom hwcomposer)
TEST=built + launch cuttlefish w/ 2D mode (minigbm + drm_hwcomposer)
TEST=built + launch cuttlefish w/ 3D mode (minigbm + drm_hwcomposer)

Change-Id: Ie5b7a6f80f7e0da72a910644ba42d2f34b246be8
Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/platform/crosvm/+/1993913
Commit-Queue: Jason Macnak <natsu@google.com>
Tested-by: kokoro <noreply+kokoro@google.com>
Reviewed-by: Zach Reizner <zachr@chromium.org>
Reviewed-by: Lingfeng Yang <lfy@google.com>
-rw-r--r--devices/src/virtio/gpu/mod.rs413
-rw-r--r--devices/src/virtio/gpu/virtio_2d_backend.rs644
-rw-r--r--devices/src/virtio/gpu/virtio_3d_backend.rs (renamed from devices/src/virtio/gpu/backend.rs)508
-rw-r--r--devices/src/virtio/gpu/virtio_backend.rs281
-rw-r--r--gpu_display/src/gpu_display_wl.rs2
-rw-r--r--src/main.rs8
6 files changed, 1469 insertions, 387 deletions
diff --git a/devices/src/virtio/gpu/mod.rs b/devices/src/virtio/gpu/mod.rs
index 877746e..aa674a8 100644
--- a/devices/src/virtio/gpu/mod.rs
+++ b/devices/src/virtio/gpu/mod.rs
@@ -2,11 +2,14 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-mod backend;
 mod protocol;
+mod virtio_2d_backend;
+mod virtio_3d_backend;
+mod virtio_backend;
 
 use std::cell::RefCell;
 use std::collections::VecDeque;
+use std::fs::File;
 use std::i64;
 use std::io::Read;
 use std::mem::{self, size_of};
@@ -19,14 +22,12 @@ use std::time::Duration;
 
 use data_model::*;
 
-use sys_util::{
-    debug, error, warn, Error, EventFd, GuestAddress, GuestMemory, PollContext, PollToken,
-};
+use sys_util::{debug, error, warn, EventFd, GuestAddress, GuestMemory, PollContext, PollToken};
 
 pub use gpu_display::EventDevice;
 use gpu_display::*;
-use gpu_renderer::{Renderer, RendererFlags};
-
+use gpu_renderer::RendererFlags;
+use msg_socket::{MsgReceiver, MsgSender};
 use resources::Alloc;
 
 use super::{
@@ -36,8 +37,9 @@ use super::{
 
 use super::{PciCapabilityType, VirtioPciShmCap, VirtioPciShmCapID};
 
-use self::backend::Backend;
 use self::protocol::*;
+use self::virtio_2d_backend::Virtio2DBackend;
+use self::virtio_3d_backend::Virtio3DBackend;
 use crate::pci::{PciBarConfiguration, PciBarPrefetchable, PciBarRegionType, PciCapability};
 
 use vm_control::VmMemoryControlRequestSocket;
@@ -45,6 +47,12 @@ use vm_control::VmMemoryControlRequestSocket;
 pub const DEFAULT_DISPLAY_WIDTH: u32 = 1280;
 pub const DEFAULT_DISPLAY_HEIGHT: u32 = 1024;
 
+#[derive(Copy, Clone, Debug, PartialEq)]
+pub enum GpuMode {
+    Mode2D,
+    Mode3D,
+}
+
 #[derive(Debug)]
 pub struct GpuParameters {
     pub display_width: u32,
@@ -53,6 +61,7 @@ pub struct GpuParameters {
     pub renderer_use_gles: bool,
     pub renderer_use_glx: bool,
     pub renderer_use_surfaceless: bool,
+    pub mode: GpuMode,
 }
 
 pub const DEFAULT_GPU_PARAMS: GpuParameters = GpuParameters {
@@ -62,6 +71,7 @@ pub const DEFAULT_GPU_PARAMS: GpuParameters = GpuParameters {
     renderer_use_gles: true,
     renderer_use_glx: false,
     renderer_use_surfaceless: true,
+    mode: GpuMode::Mode3D,
 };
 
 // First queue is for virtio gpu commands. Second queue is for cursor commands, which we expect
@@ -73,6 +83,277 @@ const GPU_BAR_NUM: u8 = 4;
 const GPU_BAR_OFFSET: u64 = 0;
 const GPU_BAR_SIZE: u64 = 1 << 33;
 
+/// A virtio-gpu backend state tracker which supports display and potentially accelerated rendering.
+///
+/// Commands from the virtio-gpu protocol can be submitted here using the methods, and they will be
+/// realized on the hardware. Most methods return a `GpuResponse` that indicate the success,
+/// failure, or requested data for the given command.
+trait Backend {
+    /// Returns the number of capsets provided by the Backend.
+    fn capsets() -> u32
+    where
+        Self: Sized;
+
+    /// Returns the bitset of virtio features provided by the Backend.
+    fn features() -> u64
+    where
+        Self: Sized;
+
+    /// Constructs a backend.
+    fn build(
+        possible_displays: &[DisplayBackend],
+        display_width: u32,
+        display_height: u32,
+        renderer_flags: RendererFlags,
+        event_devices: Vec<EventDevice>,
+        gpu_device_socket: VmMemoryControlRequestSocket,
+        pci_bar: Alloc,
+    ) -> Option<Box<dyn Backend>>
+    where
+        Self: Sized;
+
+    fn display(&self) -> &Rc<RefCell<GpuDisplay>>;
+
+    /// Processes the internal `display` events and returns `true` if the main display was closed.
+    fn process_display(&mut self) -> bool;
+
+    /// Creates a fence with the given id that can be used to determine when the previous command
+    /// completed.
+    fn create_fence(&mut self, ctx_id: u32, fence_id: u32) -> GpuResponse;
+
+    /// Returns the id of the latest fence to complete.
+    fn fence_poll(&mut self) -> u32;
+
+    /// For accelerated rendering capable backends, switch to the default rendering context.
+    fn force_ctx_0(&mut self) {}
+
+    /// Attaches the given input device to the given surface of the display (to allow for input
+    /// from a X11 window for example).
+    fn import_event_device(&mut self, event_device: EventDevice, scanout: u32);
+
+    /// If supported, export the resource with the given id to a file.
+    fn export_resource(&mut self, id: u32) -> Option<File>;
+
+    /// Gets the list of supported display resolutions as a slice of `(width, height)` tuples.
+    fn display_info(&self) -> [(u32, u32); 1];
+
+    /// Creates a 2D resource with the given properties and associates it with the given id.
+    fn create_resource_2d(&mut self, id: u32, width: u32, height: u32, format: u32) -> GpuResponse;
+
+    /// Removes the guest's reference count for the given resource id.
+    fn unref_resource(&mut self, id: u32) -> GpuResponse;
+
+    /// Sets the given resource id as the source of scanout to the display.
+    fn set_scanout(&mut self, _scanout_id: u32, resource_id: u32) -> GpuResponse;
+
+    /// Flushes the given rectangle of pixels of the given resource to the display.
+    fn flush_resource(&mut self, id: u32, x: u32, y: u32, width: u32, height: u32) -> GpuResponse;
+
+    /// Copes the given rectangle of pixels of the given resource's backing memory to the host side
+    /// resource.
+    fn transfer_to_resource_2d(
+        &mut self,
+        id: u32,
+        x: u32,
+        y: u32,
+        width: u32,
+        height: u32,
+        src_offset: u64,
+        mem: &GuestMemory,
+    ) -> GpuResponse;
+
+    /// Attaches backing memory to the given resource, represented by a `Vec` of `(address, size)`
+    /// tuples in the guest's physical address space.
+    fn attach_backing(
+        &mut self,
+        id: u32,
+        mem: &GuestMemory,
+        vecs: Vec<(GuestAddress, usize)>,
+    ) -> GpuResponse;
+
+    /// Detaches any backing memory from the given resource, if there is any.
+    fn detach_backing(&mut self, id: u32) -> GpuResponse;
+
+    /// Updates the cursor's memory to the given id, and sets its position to the given coordinates.
+    fn update_cursor(&mut self, id: u32, x: u32, y: u32) -> GpuResponse;
+
+    /// Moves the cursor's position to the given coordinates.
+    fn move_cursor(&mut self, x: u32, y: u32) -> GpuResponse;
+
+    /// Gets the renderer's capset information associated with `index`.
+    fn get_capset_info(&self, index: u32) -> GpuResponse;
+
+    /// Gets the capset of `version` associated with `id`.
+    fn get_capset(&self, id: u32, version: u32) -> GpuResponse;
+
+    /// Creates a fresh renderer context with the given `id`.
+    fn create_renderer_context(&mut self, _id: u32) -> GpuResponse {
+        GpuResponse::ErrUnspec
+    }
+
+    /// Destorys the renderer context associated with `id`.
+    fn destroy_renderer_context(&mut self, _id: u32) -> GpuResponse {
+        GpuResponse::ErrUnspec
+    }
+
+    /// Attaches the indicated resource to the given context.
+    fn context_attach_resource(&mut self, _ctx_id: u32, _res_id: u32) -> GpuResponse {
+        GpuResponse::ErrUnspec
+    }
+
+    /// detaches the indicated resource to the given context.
+    fn context_detach_resource(&mut self, _ctx_id: u32, _res_id: u32) -> GpuResponse {
+        GpuResponse::ErrUnspec
+    }
+
+    /// Creates a 3D resource with the given properties and associates it with the given id.
+    fn resource_create_3d(
+        &mut self,
+        _id: u32,
+        _target: u32,
+        _format: u32,
+        _bind: u32,
+        _width: u32,
+        _height: u32,
+        _depth: u32,
+        _array_size: u32,
+        _last_level: u32,
+        _nr_samples: u32,
+        _flags: u32,
+    ) -> GpuResponse {
+        GpuResponse::ErrUnspec
+    }
+
+    /// Copes the given 3D rectangle of pixels of the given resource's backing memory to the host
+    /// side resource.
+    fn transfer_to_resource_3d(
+        &mut self,
+        _ctx_id: u32,
+        _res_id: u32,
+        _x: u32,
+        _y: u32,
+        _z: u32,
+        _width: u32,
+        _height: u32,
+        _depth: u32,
+        _level: u32,
+        _stride: u32,
+        _layer_stride: u32,
+        _offset: u64,
+    ) -> GpuResponse {
+        GpuResponse::ErrUnspec
+    }
+
+    /// Copes the given rectangle of pixels from the resource to the given resource's backing
+    /// memory.
+    fn transfer_from_resource_3d(
+        &mut self,
+        _ctx_id: u32,
+        _res_id: u32,
+        _x: u32,
+        _y: u32,
+        _z: u32,
+        _width: u32,
+        _height: u32,
+        _depth: u32,
+        _level: u32,
+        _stride: u32,
+        _layer_stride: u32,
+        _offset: u64,
+    ) -> GpuResponse {
+        GpuResponse::ErrUnspec
+    }
+
+    /// Submits a command buffer to the given rendering context.
+    fn submit_command(&mut self, _ctx_id: u32, _commands: &mut [u8]) -> GpuResponse {
+        GpuResponse::ErrUnspec
+    }
+
+    fn allocation_metadata(
+        &mut self,
+        _request_id: u32,
+        _request: Vec<u8>,
+        mut _response: Vec<u8>,
+    ) -> GpuResponse {
+        GpuResponse::ErrUnspec
+    }
+
+    fn resource_create_v2(
+        &mut self,
+        _resource_id: u32,
+        _guest_memory_type: u32,
+        _guest_caching_type: u32,
+        _size: u64,
+        _pci_addr: u64,
+        _mem: &GuestMemory,
+        _vecs: Vec<(GuestAddress, usize)>,
+        _args: Vec<u8>,
+    ) -> GpuResponse {
+        GpuResponse::ErrUnspec
+    }
+
+    fn resource_v2_unref(&mut self, _resource_id: u32) -> GpuResponse {
+        GpuResponse::ErrUnspec
+    }
+}
+
+#[derive(Clone)]
+enum BackendKind {
+    Virtio2D,
+    Virtio3D,
+}
+
+impl BackendKind {
+    /// Returns the number of capsets provided by the Backend.
+    fn capsets(&self) -> u32 {
+        match self {
+            BackendKind::Virtio2D => Virtio2DBackend::capsets(),
+            BackendKind::Virtio3D => Virtio3DBackend::capsets(),
+        }
+    }
+
+    /// Returns the bitset of virtio features provided by the Backend.
+    fn features(&self) -> u64 {
+        match self {
+            BackendKind::Virtio2D => Virtio2DBackend::features(),
+            BackendKind::Virtio3D => Virtio3DBackend::features(),
+        }
+    }
+
+    /// Initializes the backend.
+    fn build(
+        &self,
+        possible_displays: &[DisplayBackend],
+        display_width: u32,
+        display_height: u32,
+        renderer_flags: RendererFlags,
+        event_devices: Vec<EventDevice>,
+        gpu_device_socket: VmMemoryControlRequestSocket,
+        pci_bar: Alloc,
+    ) -> Option<Box<dyn Backend>> {
+        match self {
+            BackendKind::Virtio2D => Virtio2DBackend::build(
+                possible_displays,
+                display_width,
+                display_height,
+                renderer_flags,
+                event_devices,
+                gpu_device_socket,
+                pci_bar,
+            ),
+            BackendKind::Virtio3D => Virtio3DBackend::build(
+                possible_displays,
+                display_width,
+                display_height,
+                renderer_flags,
+                event_devices,
+                gpu_device_socket,
+                pci_bar,
+            ),
+        }
+    }
+}
+
 struct ReturnDescriptor {
     index: u16,
     len: u32,
@@ -88,11 +369,11 @@ struct Frontend {
     return_ctrl_descriptors: VecDeque<ReturnDescriptor>,
     return_cursor_descriptors: VecDeque<ReturnDescriptor>,
     fence_descriptors: Vec<FenceDescriptor>,
-    backend: Backend,
+    backend: Box<dyn Backend>,
 }
 
 impl Frontend {
-    fn new(backend: Backend) -> Frontend {
+    fn new(backend: Box<dyn Backend>) -> Frontend {
         Frontend {
             return_ctrl_descriptors: Default::default(),
             return_cursor_descriptors: Default::default(),
@@ -101,7 +382,7 @@ impl Frontend {
         }
     }
 
-    fn display(&self) -> &Rc<RefCell<GpuDisplay>> {
+    fn display(&mut self) -> &Rc<RefCell<GpuDisplay>> {
         self.backend.display()
     }
 
@@ -109,8 +390,26 @@ impl Frontend {
         self.backend.process_display()
     }
 
-    fn process_resource_bridge(&self, resource_bridge: &ResourceResponseSocket) {
-        self.backend.process_resource_bridge(resource_bridge);
+    fn process_resource_bridge(&mut self, resource_bridge: &ResourceResponseSocket) {
+        let request = match resource_bridge.recv() {
+            Ok(msg) => msg,
+            Err(e) => {
+                error!("error receiving resource bridge request: {}", e);
+                return;
+            }
+        };
+
+        let response = match request {
+            ResourceRequest::GetResource { id } => self
+                .backend
+                .export_resource(id)
+                .map(ResourceResponse::Resource)
+                .unwrap_or(ResourceResponse::Invalid),
+        };
+
+        if let Err(e) = resource_bridge.send(&response) {
+            error!("error sending resource bridge request: {}", e);
+        }
     }
 
     fn process_gpu_command(
@@ -609,7 +908,9 @@ impl Worker {
                             let _ = self.exit_evt.write(1);
                         }
                     }
-                    Token::ResourceBridge { index } => process_resource_bridge[index] = true,
+                    Token::ResourceBridge { index } => {
+                        process_resource_bridge[index] = true;
+                    }
                     Token::InterruptResample => {
                         self.interrupt.interrupt_resample();
                     }
@@ -692,75 +993,6 @@ impl DisplayBackend {
     }
 }
 
-// Builds a gpu backend with one of the given possible display backends, or None if they all
-// failed.
-fn build_backend(
-    possible_displays: &[DisplayBackend],
-    display_width: u32,
-    display_height: u32,
-    renderer_flags: RendererFlags,
-    event_devices: Vec<EventDevice>,
-    gpu_device_socket: VmMemoryControlRequestSocket,
-    pci_bar: Alloc,
-) -> Option<Backend> {
-    let mut renderer_flags = renderer_flags;
-    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);
-                }
-                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;
-        }
-    };
-
-    let mut backend = Backend::new(
-        display,
-        display_width,
-        display_height,
-        renderer,
-        gpu_device_socket,
-        pci_bar,
-    );
-
-    for event_device in event_devices {
-        backend.import_event_device(event_device, 0);
-    }
-
-    Some(backend)
-}
-
 pub struct Gpu {
     exit_evt: EventFd,
     gpu_device_socket: Option<VmMemoryControlRequestSocket>,
@@ -775,6 +1007,7 @@ pub struct Gpu {
     display_height: u32,
     renderer_flags: RendererFlags,
     pci_bar: Option<Alloc>,
+    backend_kind: BackendKind,
 }
 
 impl Gpu {
@@ -793,6 +1026,11 @@ impl Gpu {
             .use_glx(gpu_parameters.renderer_use_glx)
             .use_surfaceless(gpu_parameters.renderer_use_surfaceless);
 
+        let backend_kind = match gpu_parameters.mode {
+            GpuMode::Mode2D => BackendKind::Virtio2D,
+            GpuMode::Mode3D => BackendKind::Virtio3D,
+        };
+
         Gpu {
             exit_evt,
             gpu_device_socket,
@@ -807,6 +1045,7 @@ impl Gpu {
             display_height: gpu_parameters.display_height,
             renderer_flags,
             pci_bar: None,
+            backend_kind,
         }
     }
 
@@ -819,7 +1058,7 @@ impl Gpu {
             events_read: Le32::from(events_read),
             events_clear: Le32::from(0),
             num_scanouts: Le32::from(self.num_scanouts.get() as u32),
-            num_capsets: Le32::from(3),
+            num_capsets: Le32::from(self.backend_kind.capsets()),
         }
     }
 }
@@ -867,10 +1106,7 @@ impl VirtioDevice for Gpu {
     }
 
     fn features(&self) -> u64 {
-        1 << VIRTIO_GPU_F_VIRGL
-            | 1 << VIRTIO_F_VERSION_1
-            | 1 << VIRTIO_GPU_F_MEMORY
-            | 1 << VIRTIO_GPU_F_HOST_COHERENT
+        self.backend_kind.features()
     }
 
     fn ack_features(&mut self, value: u64) {
@@ -919,6 +1155,7 @@ impl VirtioDevice for Gpu {
 
         let resource_bridges = mem::replace(&mut self.resource_bridges, Vec::new());
 
+        let backend_kind = self.backend_kind.clone();
         let ctrl_queue = queues.remove(0);
         let ctrl_evt = queue_evts.remove(0);
         let cursor_queue = queues.remove(0);
@@ -935,7 +1172,7 @@ impl VirtioDevice for Gpu {
                 thread::Builder::new()
                     .name("virtio_gpu".to_string())
                     .spawn(move || {
-                        let backend = match build_backend(
+                        let backend = match backend_kind.build(
                             &display_backends,
                             display_width,
                             display_height,
diff --git a/devices/src/virtio/gpu/virtio_2d_backend.rs b/devices/src/virtio/gpu/virtio_2d_backend.rs
new file mode 100644
index 0000000..d92b015
--- /dev/null
+++ b/devices/src/virtio/gpu/virtio_2d_backend.rs
@@ -0,0 +1,644 @@
+// Copyright 2020 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.
+
+//! Implementation of a virtio-gpu protocol command processor which supports only display.
+
+use std::cell::RefCell;
+use std::cmp::{max, min};
+use std::collections::btree_map::Entry;
+use std::collections::BTreeMap as Map;
+use std::fmt::{self, Display};
+use std::fs::File;
+use std::marker::PhantomData;
+use std::rc::Rc;
+use std::usize;
+
+use data_model::*;
+use gpu_display::*;
+use gpu_renderer::RendererFlags;
+use resources::Alloc;
+use sys_util::{error, GuestAddress, GuestMemory};
+use vm_control::VmMemoryControlRequestSocket;
+
+use super::protocol::GpuResponse;
+pub use super::virtio_backend::{VirtioBackend, VirtioResource};
+use crate::virtio::gpu::{Backend, DisplayBackend, VIRTIO_F_VERSION_1};
+
+#[derive(Debug)]
+pub enum Error {
+    CheckedArithmetic {
+        field1: (&'static str, usize),
+        field2: (&'static str, usize),
+        op: &'static str,
+    },
+    CheckedRange {
+        field1: (&'static str, usize),
+        field2: (&'static str, usize),
+    },
+    MemCopy(VolatileMemoryError),
+}
+
+impl Display for Error {
+    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+        use self::Error::*;
+
+        match self {
+            CheckedArithmetic {
+                field1: (label1, value1),
+                field2: (label2, value2),
+                op,
+            } => write!(
+                f,
+                "arithmetic failed: {}({}) {} {}({})",
+                label1, value1, op, label2, value2
+            ),
+            CheckedRange {
+                field1: (label1, value1),
+                field2: (label2, value2),
+            } => write!(
+                f,
+                "range check failed: {}({}) vs {}({})",
+                label1, value1, label2, value2
+            ),
+            MemCopy(e) => write!(f, "{}", e),
+        }
+    }
+}
+
+macro_rules! checked_arithmetic {
+    ($x:ident $op:ident $y:ident $op_name:expr) => {
+        $x.$op($y).ok_or_else(|| Error::CheckedArithmetic {
+            field1: (stringify!($x), $x as usize),
+            field2: (stringify!($y), $y as usize),
+            op: $op_name,
+        })
+    };
+    ($x:ident + $y:ident) => {
+        checked_arithmetic!($x checked_add $y "+")
+    };
+    ($x:ident - $y:ident) => {
+        checked_arithmetic!($x checked_sub $y "-")
+    };
+    ($x:ident * $y:ident) => {
+        checked_arithmetic!($x checked_mul $y "*")
+    };
+}
+
+macro_rules! checked_range {
+    ($x:expr; <= $y:expr) => {
+        if $x <= $y {
+            Ok(())
+        } else {
+            Err(Error::CheckedRange {
+                field1: (stringify!($x), $x as usize),
+                field2: (stringify!($y), $y as usize),
+            })
+        }
+    };
+    ($x:ident <= $y:ident) => {
+        check_range!($x; <= $y)
+    };
+}
+
+pub struct Virtio2DResource {
+    width: u32,
+    height: u32,
+    guest_iovecs: Vec<(GuestAddress, usize)>,
+    guest_mem: Option<GuestMemory>,
+    host_mem: Vec<u8>,
+    host_mem_stride: u32,
+    no_sync_send: PhantomData<*mut ()>,
+}
+
+/// Transfers a resource from potentially many chunked src VolatileSlices to a dst VolatileSlice.
+pub fn transfer<'a, S: Iterator<Item = VolatileSlice<'a>>>(
+    resource_w: u32,
+    resource_h: u32,
+    rect_x: u32,
+    rect_y: u32,
+    rect_w: u32,
+    rect_h: u32,
+    dst_stride: u32,
+    dst_offset: u64,
+    dst: VolatileSlice,
+    src_stride: u32,
+    src_offset: u64,
+    mut srcs: S,
+) -> Result<(), Error> {
+    if rect_w == 0 || rect_h == 0 {
+        return Ok(());
+    }
+
+    checked_range!(checked_arithmetic!(rect_x + rect_w)?; <= resource_w)?;
+    checked_range!(checked_arithmetic!(rect_y + rect_h)?; <= resource_h)?;
+
+    let bytes_per_pixel = 4 as u64;
+
+    let rect_x = rect_x as u64;
+    let rect_y = rect_y as u64;
+    let rect_w = rect_w as u64;
+    let rect_h = rect_h as u64;
+
+    let dst_stride = dst_stride as u64;
+    let dst_offset = dst_offset as u64;
+    let dst_resource_offset = dst_offset + (rect_y * dst_stride) + (rect_x * bytes_per_pixel);
+
+    let src_stride = src_stride as u64;
+    let src_offset = src_offset as u64;
+    let src_resource_offset = src_offset + (rect_y * src_stride) + (rect_x * bytes_per_pixel);
+
+    let mut next_src;
+    let mut next_line;
+    let mut current_height = 0 as u64;
+    let mut src_opt = srcs.next();
+
+    // Cumulative start offset of the current src.
+    let mut src_start_offset = 0 as u64;
+    while let Some(src) = src_opt {
+        if current_height >= rect_h {
+            break;
+        }
+
+        let src_size = src.size() as u64;
+
+        // Cumulative end offset of the current src.
+        let src_end_offset = checked_arithmetic!(src_start_offset + src_size)?;
+
+        let src_line_vertical_offset = checked_arithmetic!(current_height * src_stride)?;
+        let src_line_horizontal_offset = checked_arithmetic!(rect_w * bytes_per_pixel)?;
+
+        // Cumulative start/end offsets of the next line to copy within all srcs.
+        let src_line_start_offset =
+            checked_arithmetic!(src_resource_offset + src_line_vertical_offset)?;
+        let src_line_end_offset =
+            checked_arithmetic!(src_line_start_offset + src_line_horizontal_offset)?;
+
+        // Clamp the line start/end offset to be inside the current src.
+        let src_copyable_start_offset = max(src_line_start_offset, src_start_offset);
+        let src_copyable_end_offset = min(src_line_end_offset, src_end_offset);
+
+        if src_copyable_start_offset < src_copyable_end_offset {
+            let copyable_size =
+                checked_arithmetic!(src_copyable_end_offset - src_copyable_start_offset)?;
+
+            let offset_within_src = match src_copyable_start_offset.checked_sub(src_start_offset) {
+                Some(difference) => difference,
+                None => 0,
+            };
+
+            if src_line_end_offset > src_end_offset {
+                next_src = true;
+                next_line = false;
+            } else if src_line_end_offset == src_end_offset {
+                next_src = true;
+                next_line = true;
+            } else {
+                next_src = false;
+                next_line = true;
+            }
+
+            let src_subslice = src
+                .get_slice(offset_within_src, copyable_size)
+                .map_err(|e| Error::MemCopy(e))?;
+
+            let dst_line_vertical_offset = checked_arithmetic!(current_height * dst_stride)?;
+            let dst_line_horizontal_offset =
+                checked_arithmetic!(src_copyable_start_offset - src_line_start_offset)?;
+            let dst_line_offset =
+                checked_arithmetic!(dst_line_vertical_offset + dst_line_horizontal_offset)?;
+            let dst_start_offset = checked_arithmetic!(dst_resource_offset + dst_line_offset)?;
+
+            let dst_subslice = dst
+                .get_slice(dst_start_offset, copyable_size)
+                .map_err(|e| Error::MemCopy(e))?;
+
+            src_subslice.copy_to_volatile_slice(dst_subslice);
+        } else {
+            if src_line_start_offset >= src_start_offset {
+                next_src = true;
+                next_line = false;
+            } else {
+                next_src = false;
+                next_line = true;
+            }
+        };
+
+        if next_src {
+            src_start_offset = checked_arithmetic!(src_start_offset + src_size)?;
+            src_opt = srcs.next();
+        }
+
+        if next_line {
+            current_height += 1;
+        }
+    }
+
+    Ok(())
+}
+
+impl Virtio2DResource {
+    /// Attaches scatter-gather memory to this resource.
+    pub fn attach_backing(
+        &mut self,
+        iovecs: Vec<(GuestAddress, usize)>,
+        mem: &GuestMemory,
+    ) -> bool {
+        if iovecs
+            .iter()
+            .any(|&(addr, len)| mem.get_slice(addr.offset(), len as u64).is_err())
+        {
+            return false;
+        }
+        self.detach_backing();
+        self.guest_mem = Some(mem.clone());
+        for (addr, len) in iovecs {
+            self.guest_iovecs.push((addr, len));
+        }
+        true
+    }
+
+    /// Detaches previously attached scatter-gather memory from this resource.
+    pub fn detach_backing(&mut self) {
+        self.guest_iovecs.clear();
+        self.guest_mem = None;
+    }
+
+    fn as_mut(&mut self) -> &mut dyn VirtioResource {
+        self
+    }
+}
+
+impl VirtioResource for Virtio2DResource {
+    fn width(&self) -> u32 {
+        self.width
+    }
+
+    fn height(&self) -> u32 {
+        self.height
+    }
+
+    fn import_to_display(&mut self, _display: &Rc<RefCell<GpuDisplay>>) -> Option<u32> {
+        None
+    }
+
+    /// Performs a transfer to the given host side resource from its backing in guest memory.
+    fn write_from_guest_memory(
+        &mut self,
+        x: u32,
+        y: u32,
+        width: u32,
+        height: u32,
+        src_offset: u64,
+        _mem: &GuestMemory,
+    ) {
+        let guest_mem = match &self.guest_mem {
+            Some(mem) => mem,
+            None => {
+                error!("failed to write to resource: no guest memory attached");
+                return;
+            }
+        };
+
+        if self
+            .guest_iovecs
+            .iter()
+            .any(|&(addr, len)| guest_mem.get_slice(addr.offset(), len as u64).is_err())
+        {
+            error!("failed to write to resource: invalid iovec attached");
+            return;
+        }
+
+        let mut src_slices = Vec::new();
+        for (addr, len) in &self.guest_iovecs {
+            // Unwrap will not panic because we already checked the slices.
+            src_slices.push(guest_mem.get_slice(addr.offset(), *len as u64).unwrap());
+        }
+
+        let host_mem_len = self.host_mem.len() as u64;
+
+        let src_stride = self.host_mem_stride;
+        let src_offset = src_offset;
+
+        let dst_stride = self.host_mem_stride;
+        let dst_offset = 0;
+
+        if let Err(e) = transfer(
+            self.width(),
+            self.height(),
+            x,
+            y,
+            width,
+            height,
+            dst_stride,
+            dst_offset,
+            self.host_mem
+                .as_mut_slice()
+                .get_slice(0, host_mem_len)
+                .unwrap(),
+            src_stride,
+            src_offset,
+            src_slices.iter().cloned(),
+        ) {
+            error!("failed to write to resource: {}", e);
+        }
+    }
+
+    /// Reads from this host side resource to a volatile slice of memory.
+    fn read_to_volatile(
+        &mut self,
+        x: u32,
+        y: u32,
+        width: u32,
+        height: u32,
+        dst: VolatileSlice,
+        dst_stride: u32,
+    ) {
+        let src_stride = self.host_mem_stride;
+        let src_offset = 0;
+
+        let dst_offset = 0;
+
+        let host_mem_len = self.host_mem.len() as u64;
+
+        if let Err(e) = transfer(
+            self.width(),
+            self.height(),
+            x,
+            y,
+            width,
+            height,
+            dst_stride,
+            dst_offset,
+            dst,
+            src_stride,
+            src_offset,
+            [self
+                .host_mem
+                .as_mut_slice()
+                .get_slice(0, host_mem_len)
+                .unwrap()]
+            .iter()
+            .cloned(),
+        ) {
+            error!("failed to read from resource: {}", e);
+        }
+    }
+}
+
+/// The virtio-gpu backend state tracker which does not support accelerated rendering.
+pub struct Virtio2DBackend {
+    base: VirtioBackend,
+    resources: Map<u32, Virtio2DResource>,
+    /// All commands processed by this 2D backend are synchronous and are completed immediately so
+    /// we just need to keep track of the latest created fence and return that in fence_poll().
+    latest_created_fence_id: u32,
+}
+
+impl Virtio2DBackend {
+    pub fn new(display: GpuDisplay, display_width: u32, display_height: u32) -> Virtio2DBackend {
+        Virtio2DBackend {
+            base: VirtioBackend {
+                display: Rc::new(RefCell::new(display)),
+                display_width,
+                display_height,
+                event_devices: Default::default(),
+                scanout_resource_id: None,
+                scanout_surface_id: None,
+                cursor_resource_id: None,
+                cursor_surface_id: None,
+            },
+            resources: Default::default(),
+            latest_created_fence_id: 0,
+        }
+    }
+}
+
+impl Backend for Virtio2DBackend {
+    /// Returns the number of capsets provided by the Backend.
+    fn capsets() -> u32 {
+        0
+    }
+
+    /// Returns the bitset of virtio features provided by the Backend.
+    fn features() -> u64 {
+        1 << VIRTIO_F_VERSION_1
+    }
+
+    /// Returns the underlying Backend.
+    fn build(
+        possible_displays: &[DisplayBackend],
+        display_width: u32,
+        display_height: u32,
+        _renderer_flags: RendererFlags,
+        _event_devices: Vec<EventDevice>,
+        _gpu_device_socket: VmMemoryControlRequestSocket,
+        _pci_bar: Alloc,
+    ) -> Option<Box<dyn Backend>> {
+        let mut display_opt = None;
+        for display in possible_displays {
+            match display.build() {
+                Ok(c) => {
+                    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;
+            }
+        };
+
+        Some(Box::new(Virtio2DBackend::new(
+            display,
+            display_width,
+            display_height,
+        )))
+    }
+
+    fn display(&self) -> &Rc<RefCell<GpuDisplay>> {
+        &self.base.display
+    }
+
+    /// Processes the internal `display` events and returns `true` if the main display was closed.
+    fn process_display(&mut self) -> bool {
+        self.base.process_display()
+    }
+
+    /// Gets the list of supported display resolutions as a slice of `(width, height)` tuples.
+    fn display_info(&self) -> [(u32, u32); 1] {
+        self.base.display_info()
+    }
+
+    /// Attaches the given input device to the given surface of the display (to allow for input
+    /// from a X11 window for example).
+    fn import_event_device(&mut self, event_device: EventDevice, scanout: u32) {
+        self.base.import_event_device(event_device, scanout);
+    }
+
+    /// If supported, export the resource with the given id to a file.
+    fn export_resource(&mut self, _id: u32) -> Option<File> {
+        None
+    }
+
+    /// Creates a fence with the given id that can be used to determine when the previous command
+    /// completed.
+    fn create_fence(&mut self, _ctx_id: u32, fence_id: u32) -> GpuResponse {
+        self.latest_created_fence_id = fence_id;
+
+        GpuResponse::OkNoData
+    }
+
+    /// Returns the id of the latest fence to complete.
+    fn fence_poll(&mut self) -> u32 {
+        self.latest_created_fence_id
+    }
+
+    fn create_resource_2d(
+        &mut self,
+        id: u32,
+        width: u32,
+        height: u32,
+        _format: u32,
+    ) -> GpuResponse {
+        if id == 0 {
+            return GpuResponse::ErrInvalidResourceId;
+        }
+        match self.resources.entry(id) {
+            Entry::Vacant(slot) => {
+                // All virtio formats are 4 bytes per pixel.
+                let resource_bpp = 4;
+                let resource_stride = resource_bpp * width;
+                let resource_size = (resource_stride as usize) * (height as usize);
+
+                let gpu_resource = Virtio2DResource {
+                    width,
+                    height,
+                    guest_iovecs: Vec::new(),
+                    guest_mem: None,
+                    host_mem: vec![0; resource_size],
+                    host_mem_stride: resource_stride,
+                    no_sync_send: PhantomData,
+                };
+                slot.insert(gpu_resource);
+                GpuResponse::OkNoData
+            }
+            Entry::Occupied(_) => GpuResponse::ErrInvalidResourceId,
+        }
+    }
+
+    /// Removes the guest's reference count for the given resource id.
+    fn unref_resource(&mut self, id: u32) -> GpuResponse {
+        match self.resources.remove(&id) {
+            Some(_) => GpuResponse::OkNoData,
+            None => GpuResponse::ErrInvalidResourceId,
+        }
+    }
+
+    /// Sets the given resource id as the source of scanout to the display.
+    fn set_scanout(&mut self, _scanout_id: u32, resource_id: u32) -> GpuResponse {
+        if resource_id == 0 || self.resources.get_mut(&resource_id).is_some() {
+            self.base.set_scanout(resource_id)
+        } else {
+            GpuResponse::ErrInvalidResourceId
+        }
+    }
+
+    /// Flushes the given rectangle of pixels of the given resource to the display.
+    fn flush_resource(
+        &mut self,
+        id: u32,
+        _x: u32,
+        _y: u32,
+        _width: u32,
+        _height: u32,
+    ) -> GpuResponse {
+        if id == 0 {
+            return GpuResponse::OkNoData;
+        }
+
+        let resource = match self.resources.get_mut(&id) {
+            Some(r) => r,
+            None => return GpuResponse::ErrInvalidResourceId,
+        };
+
+        self.base.flush_resource(resource, id)
+    }
+
+    /// Copes the given rectangle of pixels of the given resource's backing memory to the host side
+    /// resource.
+    fn transfer_to_resource_2d(
+        &mut self,
+        id: u32,
+        x: u32,
+        y: u32,
+        width: u32,
+        height: u32,
+        src_offset: u64,
+        mem: &GuestMemory,
+    ) -> GpuResponse {
+        if let Some(resource) = self.resources.get_mut(&id) {
+            resource.write_from_guest_memory(x, y, width, height, src_offset, mem);
+            GpuResponse::OkNoData
+        } else {
+            GpuResponse::ErrInvalidResourceId
+        }
+    }
+
+    /// Attaches backing memory to the given resource, represented by a `Vec` of `(address, size)`
+    /// tuples in the guest's physical address space.
+    fn attach_backing(
+        &mut self,
+        id: u32,
+        mem: &GuestMemory,
+        vecs: Vec<(GuestAddress, usize)>,
+    ) -> GpuResponse {
+        match self.resources.get_mut(&id) {
+            Some(resource) => {
+                if resource.attach_backing(vecs, mem) {
+                    GpuResponse::OkNoData
+                } else {
+                    GpuResponse::ErrUnspec
+                }
+            }
+            None => GpuResponse::ErrInvalidResourceId,
+        }
+    }
+
+    /// Detaches any backing memory from the given resource, if there is any.
+    fn detach_backing(&mut self, id: u32) -> GpuResponse {
+        match self.resources.get_mut(&id) {
+            Some(resource) => {
+                resource.detach_backing();
+                GpuResponse::OkNoData
+            }
+            None => GpuResponse::ErrInvalidResourceId,
+        }
+    }
+
+    /// Updates the cursor's memory to the given id, and sets its position to the given coordinates.
+    fn update_cursor(&mut self, id: u32, x: u32, y: u32) -> GpuResponse {
+        let resource = self.resources.get_mut(&id).map(|r| r.as_mut());
+
+        self.base.update_cursor(id, x, y, resource)
+    }
+
+    /// Moves the cursor's position to the given coordinates.
+    fn move_cursor(&mut self, x: u32, y: u32) -> GpuResponse {
+        self.base.move_cursor(x, y)
+    }
+
+    /// Gets the renderer's capset information associated with `index`.
+    fn get_capset_info(&self, _index: u32) -> GpuResponse {
+        GpuResponse::ErrUnspec
+    }
+
+    /// Gets the capset of `version` associated with `id`.
+    fn get_capset(&self, _id: u32, _version: u32) -> GpuResponse {
+        GpuResponse::ErrUnspec
+    }
+}
diff --git a/devices/src/virtio/gpu/backend.rs b/devices/src/virtio/gpu/virtio_3d_backend.rs
index 8845612..21a5ac1 100644
--- a/devices/src/virtio/gpu/backend.rs
+++ b/devices/src/virtio/gpu/virtio_3d_backend.rs
@@ -2,11 +2,13 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-//! Implementation for the transport agnostic virtio-gpu protocol, including display and rendering.
+//! Implementation of a virtio-gpu protocol command processor which supports display and accelerated
+//! rendering.
 
 use std::cell::RefCell;
 use std::collections::btree_map::Entry;
 use std::collections::BTreeMap as Map;
+use std::fs::File;
 use std::os::unix::io::AsRawFd;
 use std::rc::Rc;
 use std::usize;
@@ -14,14 +16,13 @@ use std::usize;
 use libc::EINVAL;
 
 use data_model::*;
-
 use msg_socket::{MsgReceiver, MsgSender};
 use resources::Alloc;
-use sys_util::{error, GuestAddress, GuestMemory};
+use sys_util::{error, warn, Error, GuestAddress, GuestMemory};
 
 use gpu_display::*;
 use gpu_renderer::{
-    Box3, Context as RendererContext, Error as GpuRendererError, Renderer,
+    Box3, Context as RendererContext, Error as GpuRendererError, Renderer, RendererFlags,
     Resource as GpuRendererResource, ResourceCreateArgs,
 };
 
@@ -29,11 +30,15 @@ use super::protocol::{
     AllocationMetadataResponse, GpuResponse, GpuResponsePlaneInfo, VIRTIO_GPU_CAPSET3,
     VIRTIO_GPU_CAPSET_VIRGL, VIRTIO_GPU_CAPSET_VIRGL2, VIRTIO_GPU_MEMORY_HOST_COHERENT,
 };
-use crate::virtio::resource_bridge::*;
+pub use crate::virtio::gpu::virtio_backend::{VirtioBackend, VirtioResource};
+use crate::virtio::gpu::{
+    Backend, DisplayBackend, VIRTIO_F_VERSION_1, VIRTIO_GPU_F_HOST_COHERENT, VIRTIO_GPU_F_MEMORY,
+    VIRTIO_GPU_F_VIRGL,
+};
 
 use vm_control::{MaybeOwnedFd, VmMemoryControlRequestSocket, VmMemoryRequest, VmMemoryResponse};
 
-struct VirtioGpuResource {
+struct Virtio3DResource {
     width: u32,
     height: u32,
     gpu_resource: GpuRendererResource,
@@ -41,9 +46,9 @@ struct VirtioGpuResource {
     kvm_slot: Option<u32>,
 }
 
-impl VirtioGpuResource {
-    pub fn new(width: u32, height: u32, gpu_resource: GpuRendererResource) -> VirtioGpuResource {
-        VirtioGpuResource {
+impl Virtio3DResource {
+    pub fn new(width: u32, height: u32, gpu_resource: GpuRendererResource) -> Virtio3DResource {
+        Virtio3DResource {
             width,
             height,
             gpu_resource,
@@ -57,8 +62,8 @@ impl VirtioGpuResource {
         height: u32,
         kvm_slot: u32,
         gpu_resource: GpuRendererResource,
-    ) -> VirtioGpuResource {
-        VirtioGpuResource {
+    ) -> Virtio3DResource {
+        Virtio3DResource {
             width,
             height,
             gpu_resource,
@@ -67,7 +72,21 @@ impl VirtioGpuResource {
         }
     }
 
-    pub fn import_to_display(&mut self, display: &Rc<RefCell<GpuDisplay>>) -> Option<u32> {
+    fn as_mut(&mut self) -> &mut dyn VirtioResource {
+        self
+    }
+}
+
+impl VirtioResource for Virtio3DResource {
+    fn width(&self) -> u32 {
+        self.width
+    }
+
+    fn height(&self) -> u32 {
+        self.height
+    }
+
+    fn import_to_display(&mut self, display: &Rc<RefCell<GpuDisplay>>) -> Option<u32> {
         if let Some((self_display, import)) = &self.display_import {
             if Rc::ptr_eq(self_display, display) {
                 return Some(*import);
@@ -103,7 +122,7 @@ impl VirtioGpuResource {
         }
     }
 
-    pub fn write_from_guest_memory(
+    fn write_from_guest_memory(
         &mut self,
         x: u32,
         y: u32,
@@ -128,7 +147,7 @@ impl VirtioGpuResource {
         }
     }
 
-    pub fn read_to_volatile(
+    fn read_to_volatile(
         &mut self,
         x: u32,
         y: u32,
@@ -152,29 +171,17 @@ impl VirtioGpuResource {
     }
 }
 
-/// The virtio-gpu backend state tracker.
-///
-/// Commands from the virtio-gpu protocol can be submitted here using the methods, and they will be
-/// realized on the hardware. Most methods return a `GpuResponse` that indicate the success,
-/// failure, or requested data for the given command.
-pub struct Backend {
-    display: Rc<RefCell<GpuDisplay>>,
-    display_width: u32,
-    display_height: u32,
+/// The virtio-gpu backend state tracker which supports accelerated rendering.
+pub struct Virtio3DBackend {
+    base: VirtioBackend,
     renderer: Renderer,
-    resources: Map<u32, VirtioGpuResource>,
+    resources: Map<u32, Virtio3DResource>,
     contexts: Map<u32, RendererContext>,
-    // Maps event devices to scanout number.
-    event_devices: Map<u32, u32>,
     gpu_device_socket: VmMemoryControlRequestSocket,
-    scanout_surface: Option<u32>,
-    cursor_surface: Option<u32>,
-    scanout_resource: u32,
-    cursor_resource: u32,
     pci_bar: Alloc,
 }
 
-impl Backend {
+impl Virtio3DBackend {
     /// Creates a new backend for virtio-gpu that realizes all commands using the given `display`
     /// for showing the results, and `renderer` for submitting rendering commands.
     ///
@@ -187,94 +194,166 @@ impl Backend {
         renderer: Renderer,
         gpu_device_socket: VmMemoryControlRequestSocket,
         pci_bar: Alloc,
-    ) -> Backend {
-        Backend {
-            display: Rc::new(RefCell::new(display)),
-            display_width,
-            display_height,
+    ) -> Virtio3DBackend {
+        Virtio3DBackend {
+            base: VirtioBackend {
+                display: Rc::new(RefCell::new(display)),
+                display_width,
+                display_height,
+                event_devices: Default::default(),
+                scanout_resource_id: None,
+                scanout_surface_id: None,
+                cursor_resource_id: None,
+                cursor_surface_id: None,
+            },
             renderer,
             resources: Default::default(),
             contexts: Default::default(),
-            event_devices: Default::default(),
             gpu_device_socket,
-            scanout_surface: None,
-            cursor_surface: None,
-            scanout_resource: 0,
-            cursor_resource: 0,
             pci_bar,
         }
     }
+}
 
-    /// Gets a reference to the display passed into `new`.
-    pub fn display(&self) -> &Rc<RefCell<GpuDisplay>> {
-        &self.display
+impl Backend for Virtio3DBackend {
+    /// Returns the number of capsets provided by the Backend.
+    fn capsets() -> u32 {
+        3
+    }
+
+    /// Returns the bitset of virtio features provided by the Backend.
+    fn features() -> u64 {
+        1 << VIRTIO_GPU_F_VIRGL
+            | 1 << VIRTIO_F_VERSION_1
+            | 1 << VIRTIO_GPU_F_MEMORY
+            | 1 << VIRTIO_GPU_F_HOST_COHERENT
     }
 
-    pub fn import_event_device(&mut self, event_device: EventDevice, scanout: u32) {
-        // TODO(zachr): support more than one scanout.
-        if scanout != 0 {
-            return;
+    /// Returns the underlying Backend.
+    fn build(
+        possible_displays: &[DisplayBackend],
+        display_width: u32,
+        display_height: u32,
+        renderer_flags: RendererFlags,
+        event_devices: Vec<EventDevice>,
+        gpu_device_socket: VmMemoryControlRequestSocket,
+        pci_bar: Alloc,
+    ) -> Option<Box<dyn Backend>> {
+        let mut renderer_flags = renderer_flags;
+        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);
+                    }
+                    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 mut display = self.display.borrow_mut();
-        let event_device_id = match display.import_event_device(event_device) {
-            Ok(id) => id,
+        let renderer = match Renderer::init(renderer_flags) {
+            Ok(r) => r,
             Err(e) => {
-                error!("error importing event device: {}", e);
-                return;
+                error!("failed to initialize gpu renderer: {}", e);
+                return None;
             }
         };
-        self.scanout_surface
-            .map(|s| display.attach_event_device(s, event_device_id));
-        self.event_devices.insert(event_device_id, scanout);
+
+        let mut backend_3d = Virtio3DBackend::new(
+            display,
+            display_width,
+            display_height,
+            renderer,
+            gpu_device_socket,
+            pci_bar,
+        );
+
+        for event_device in event_devices {
+            backend_3d.import_event_device(event_device, 0);
+        }
+
+        Some(Box::new(backend_3d))
+    }
+
+    /// Gets a reference to the display passed into `new`.
+    fn display(&self) -> &Rc<RefCell<GpuDisplay>> {
+        &self.base.display
     }
 
     /// Processes the internal `display` events and returns `true` if the main display was closed.
-    pub fn process_display(&mut self) -> bool {
-        let mut display = self.display.borrow_mut();
-        display.dispatch_events();
-        self.scanout_surface
-            .map(|s| display.close_requested(s))
-            .unwrap_or(false)
+    fn process_display(&mut self) -> bool {
+        self.base.process_display()
     }
 
-    pub fn process_resource_bridge(&self, resource_bridge: &ResourceResponseSocket) {
-        let request = match resource_bridge.recv() {
-            Ok(msg) => msg,
-            Err(e) => {
-                error!("error receiving resource bridge request: {}", e);
-                return;
-            }
-        };
+    /// Gets the list of supported display resolutions as a slice of `(width, height)` tuples.
+    fn display_info(&self) -> [(u32, u32); 1] {
+        self.base.display_info()
+    }
 
-        let response = match request {
-            ResourceRequest::GetResource { id } => self
-                .resources
-                .get(&id)
-                .and_then(|resource| resource.gpu_resource.export().ok())
-                .and_then(|export| Some(export.1))
-                .map(ResourceResponse::Resource)
-                .unwrap_or(ResourceResponse::Invalid),
-        };
+    /// Attaches the given input device to the given surface of the display (to allow for input
+    /// from an X11 window for example).
+    fn import_event_device(&mut self, event_device: EventDevice, scanout: u32) {
+        self.base.import_event_device(event_device, scanout);
+    }
 
-        if let Err(e) = resource_bridge.send(&response) {
-            error!("error sending resource bridge request: {}", e);
+    /// If supported, export the resource with the given id to a file.
+    fn export_resource(&mut self, id: u32) -> Option<File> {
+        let test: Option<File> = self
+            .resources
+            .get(&id) // Option<resource>
+            .and_then(|resource| resource.gpu_resource.export().ok()) // Option<(Query, File)>
+            .and_then(|t| Some(t.1)); // Option<File>
+        return test;
+    }
+
+    /// Creates a fence with the given id that can be used to determine when the previous command
+    /// completed.
+    fn create_fence(&mut self, ctx_id: u32, fence_id: u32) -> GpuResponse {
+        // There is a mismatch of ordering that is intentional.
+        // This create_fence matches the other functions in Backend, yet
+        // the renderer matches the virgl interface.
+        match self.renderer.create_fence(fence_id, ctx_id) {
+            Ok(_) => GpuResponse::OkNoData,
+            Err(e) => {
+                error!("failed to create fence: {}", e);
+                GpuResponse::ErrUnspec
+            }
         }
     }
 
-    /// Gets the list of supported display resolutions as a slice of `(width, height)` tuples.
-    pub fn display_info(&self) -> [(u32, u32); 1] {
-        [(self.display_width, self.display_height)]
+    /// Returns the id of the latest fence to complete.
+    fn fence_poll(&mut self) -> u32 {
+        self.renderer.poll()
+    }
+
+    fn force_ctx_0(&mut self) {
+        self.renderer.force_ctx_0();
     }
 
     /// Creates a 2D resource with the given properties and associated it with the given id.
-    pub fn create_resource_2d(
-        &mut self,
-        id: u32,
-        width: u32,
-        height: u32,
-        format: u32,
-    ) -> GpuResponse {
+    fn create_resource_2d(&mut self, id: u32, width: u32, height: u32, format: u32) -> GpuResponse {
         if id == 0 {
             return GpuResponse::ErrInvalidResourceId;
         }
@@ -284,7 +363,7 @@ impl Backend {
                 match gpu_resource {
                     Ok(gpu_resource) => {
                         let virtio_gpu_resource =
-                            VirtioGpuResource::new(width, height, gpu_resource);
+                            Virtio3DResource::new(width, height, gpu_resource);
                         slot.insert(virtio_gpu_resource);
                         GpuResponse::OkNoData
                     }
@@ -299,7 +378,7 @@ impl Backend {
     }
 
     /// Removes the guest's reference count for the given resource id.
-    pub fn unref_resource(&mut self, id: u32) -> GpuResponse {
+    fn unref_resource(&mut self, id: u32) -> GpuResponse {
         match self.resources.remove(&id) {
             Some(_) => GpuResponse::OkNoData,
             None => GpuResponse::ErrInvalidResourceId,
@@ -307,130 +386,38 @@ impl Backend {
     }
 
     /// Sets the given resource id as the source of scanout to the display.
-    pub fn set_scanout(&mut self, _scanout_id: u32, resource_id: u32) -> GpuResponse {
-        let mut display = self.display.borrow_mut();
-        if resource_id == 0 {
-            if let Some(surface) = self.scanout_surface.take() {
-                display.release_surface(surface);
-            }
-            self.scanout_resource = 0;
-            if let Some(surface) = self.cursor_surface.take() {
-                display.release_surface(surface);
-            }
-            self.cursor_resource = 0;
-            GpuResponse::OkNoData
-        } else if self.resources.get_mut(&resource_id).is_some() {
-            self.scanout_resource = resource_id;
-
-            if self.scanout_surface.is_none() {
-                match display.create_surface(None, self.display_width, self.display_height) {
-                    Ok(surface) => {
-                        // TODO(zachr): do not assume every event device belongs to this scanout_id;
-                        // in other words, support multiple display outputs.
-                        for &event_device in self.event_devices.keys() {
-                            display.attach_event_device(surface, event_device);
-                        }
-                        self.scanout_surface = Some(surface);
-                    }
-                    Err(e) => error!("failed to create display surface: {}", e),
-                }
-            }
-            GpuResponse::OkNoData
+    fn set_scanout(&mut self, _scanout_id: u32, resource_id: u32) -> GpuResponse {
+        if resource_id == 0 || self.resources.get_mut(&resource_id).is_some() {
+            self.base.set_scanout(resource_id)
         } else {
             GpuResponse::ErrInvalidResourceId
         }
     }
 
-    fn flush_resource_to_surface(
+    /// Flushes the given rectangle of pixels of the given resource to the display.
+    fn flush_resource(
         &mut self,
-        resource_id: u32,
-        surface_id: u32,
+        id: u32,
         _x: u32,
         _y: u32,
         _width: u32,
         _height: u32,
     ) -> GpuResponse {
-        let resource = match self.resources.get_mut(&resource_id) {
-            Some(r) => r,
-            None => return GpuResponse::ErrInvalidResourceId,
-        };
-
-        if let Some(import_id) = resource.import_to_display(&self.display) {
-            self.display.borrow_mut().flip_to(surface_id, import_id);
-            return GpuResponse::OkNoData;
-        }
-
-        // Import failed, fall back to a copy.
-        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_region(
-            surface_id,
-            0,
-            0,
-            self.display_width,
-            self.display_height,
-        ) {
-            Some(fb) => fb,
-            None => {
-                error!("failed to access framebuffer for surface {}", surface_id);
-                return GpuResponse::ErrUnspec;
-            }
-        };
-
-        resource.read_to_volatile(
-            0,
-            0,
-            self.display_width,
-            self.display_height,
-            fb.as_volatile_slice(),
-            fb.stride(),
-        );
-        display.flip(surface_id);
-
-        GpuResponse::OkNoData
-    }
-
-    /// Flushes the given rectangle of pixels of the given resource to the display.
-    pub fn flush_resource(
-        &mut self,
-        id: u32,
-        x: u32,
-        y: u32,
-        width: u32,
-        height: u32,
-    ) -> GpuResponse {
         if id == 0 {
             return GpuResponse::OkNoData;
         }
 
-        let mut response = GpuResponse::OkNoData;
-
-        if id == self.scanout_resource {
-            if let Some(surface_id) = self.scanout_surface {
-                response = self.flush_resource_to_surface(id, surface_id, x, y, width, height);
-            }
-        }
-
-        if response != GpuResponse::OkNoData {
-            return response;
-        }
-
-        if id == self.cursor_resource {
-            if let Some(surface_id) = self.cursor_surface {
-                response = self.flush_resource_to_surface(id, surface_id, x, y, width, height);
-            }
-        }
+        let resource = match self.resources.get_mut(&id) {
+            Some(r) => r,
+            None => return GpuResponse::ErrInvalidResourceId,
+        };
 
-        response
+        self.base.flush_resource(resource, id)
     }
 
     /// Copes the given rectangle of pixels of the given resource's backing memory to the host side
     /// resource.
-    pub fn transfer_to_resource_2d(
+    fn transfer_to_resource_2d(
         &mut self,
         id: u32,
         x: u32,
@@ -451,7 +438,7 @@ impl Backend {
 
     /// Attaches backing memory to the given resource, represented by a `Vec` of `(address, size)`
     /// tuples in the guest's physical address space.
-    pub fn attach_backing(
+    fn attach_backing(
         &mut self,
         id: u32,
         mem: &GuestMemory,
@@ -467,7 +454,7 @@ impl Backend {
     }
 
     /// Detaches any backing memory from the given resource, if there is any.
-    pub fn detach_backing(&mut self, id: u32) -> GpuResponse {
+    fn detach_backing(&mut self, id: u32) -> GpuResponse {
         match self.resources.get_mut(&id) {
             Some(resource) => {
                 resource.gpu_resource.detach_backing();
@@ -478,71 +465,19 @@ impl Backend {
     }
 
     /// Updates the cursor's memory to the given id, and sets its position to the given coordinates.
-    pub fn update_cursor(&mut self, id: u32, x: u32, y: u32) -> GpuResponse {
-        if id == 0 {
-            if let Some(surface) = self.cursor_surface.take() {
-                self.display.borrow_mut().release_surface(surface);
-            }
-            self.cursor_resource = 0;
-            GpuResponse::OkNoData
-        } else if let Some(resource) = self.resources.get_mut(&id) {
-            self.cursor_resource = id;
-            if self.cursor_surface.is_none() {
-                match self.display.borrow_mut().create_surface(
-                    self.scanout_surface,
-                    resource.width,
-                    resource.height,
-                ) {
-                    Ok(surface) => self.cursor_surface = Some(surface),
-                    Err(e) => {
-                        error!("failed to create cursor surface: {}", e);
-                        return GpuResponse::ErrUnspec;
-                    }
-                }
-            }
-
-            let cursor_surface = self.cursor_surface.unwrap();
-            self.display.borrow_mut().set_position(cursor_surface, x, y);
-
-            // Gets the resource's pixels into the display by importing the buffer.
-            if let Some(import_id) = resource.import_to_display(&self.display) {
-                self.display.borrow_mut().flip_to(cursor_surface, import_id);
-                return GpuResponse::OkNoData;
-            }
+    fn update_cursor(&mut self, id: u32, x: u32, y: u32) -> GpuResponse {
+        let resource = self.resources.get_mut(&id).map(|r| r.as_mut());
 
-            // Importing failed, so try copying the pixels into the surface's slower shared memory
-            // framebuffer.
-            if let Some(fb) = self.display.borrow_mut().framebuffer(cursor_surface) {
-                resource.read_to_volatile(
-                    0,
-                    0,
-                    resource.width,
-                    resource.height,
-                    fb.as_volatile_slice(),
-                    fb.stride(),
-                )
-            }
-            self.display.borrow_mut().flip(cursor_surface);
-            GpuResponse::OkNoData
-        } else {
-            GpuResponse::ErrInvalidResourceId
-        }
+        self.base.update_cursor(id, x, y, resource)
     }
 
     /// Moves the cursor's position to the given coordinates.
-    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 mut display = self.display.borrow_mut();
-                display.set_position(cursor_surface, x, y);
-                display.commit(scanout_surface);
-            }
-        }
-        GpuResponse::OkNoData
+    fn move_cursor(&mut self, x: u32, y: u32) -> GpuResponse {
+        self.base.move_cursor(x, y)
     }
 
     /// Gets the renderer's capset information associated with `index`.
-    pub fn get_capset_info(&self, index: u32) -> GpuResponse {
+    fn get_capset_info(&self, index: u32) -> GpuResponse {
         let id = match index {
             0 => VIRTIO_GPU_CAPSET_VIRGL,
             1 => VIRTIO_GPU_CAPSET_VIRGL2,
@@ -555,12 +490,12 @@ impl Backend {
     }
 
     /// Gets the capset of `version` associated with `id`.
-    pub fn get_capset(&self, id: u32, version: u32) -> GpuResponse {
+    fn get_capset(&self, id: u32, version: u32) -> GpuResponse {
         GpuResponse::OkCapset(self.renderer.get_cap_set(id, version))
     }
 
     /// Creates a fresh renderer context with the given `id`.
-    pub fn create_renderer_context(&mut self, id: u32) -> GpuResponse {
+    fn create_renderer_context(&mut self, id: u32) -> GpuResponse {
         if id == 0 {
             return GpuResponse::ErrInvalidContextId;
         }
@@ -580,7 +515,7 @@ impl Backend {
     }
 
     /// Destorys the renderer context associated with `id`.
-    pub fn destroy_renderer_context(&mut self, id: u32) -> GpuResponse {
+    fn destroy_renderer_context(&mut self, id: u32) -> GpuResponse {
         match self.contexts.remove(&id) {
             Some(_) => GpuResponse::OkNoData,
             None => GpuResponse::ErrInvalidContextId,
@@ -588,7 +523,7 @@ impl Backend {
     }
 
     /// Attaches the indicated resource to the given context.
-    pub fn context_attach_resource(&mut self, ctx_id: u32, res_id: u32) -> GpuResponse {
+    fn context_attach_resource(&mut self, ctx_id: u32, res_id: u32) -> GpuResponse {
         match (
             self.contexts.get_mut(&ctx_id),
             self.resources.get_mut(&res_id),
@@ -603,7 +538,7 @@ impl Backend {
     }
 
     /// detaches the indicated resource to the given context.
-    pub fn context_detach_resource(&mut self, ctx_id: u32, res_id: u32) -> GpuResponse {
+    fn context_detach_resource(&mut self, ctx_id: u32, res_id: u32) -> GpuResponse {
         match (
             self.contexts.get_mut(&ctx_id),
             self.resources.get_mut(&res_id),
@@ -618,7 +553,7 @@ impl Backend {
     }
 
     /// Creates a 3D resource with the given properties and associated it with the given id.
-    pub fn resource_create_3d(
+    fn resource_create_3d(
         &mut self,
         id: u32,
         target: u32,
@@ -682,7 +617,7 @@ impl Backend {
                         };
 
                         let virtio_gpu_resource =
-                            VirtioGpuResource::new(width, height, gpu_resource);
+                            Virtio3DResource::new(width, height, gpu_resource);
                         slot.insert(virtio_gpu_resource);
                         response
                     }
@@ -696,7 +631,7 @@ impl Backend {
     }
     /// Copes the given 3D rectangle of pixels of the given resource's backing memory to the host
     /// side resource.
-    pub fn transfer_to_resource_3d(
+    fn transfer_to_resource_3d(
         &mut self,
         ctx_id: u32,
         res_id: u32,
@@ -750,7 +685,7 @@ impl Backend {
 
     /// Copes the given rectangle of pixels from the resource to the given resource's backing
     /// memory.
-    pub fn transfer_from_resource_3d(
+    fn transfer_from_resource_3d(
         &mut self,
         ctx_id: u32,
         res_id: u32,
@@ -803,7 +738,7 @@ impl Backend {
     }
 
     /// Submits a command buffer to the given rendering context.
-    pub fn submit_command(&mut self, ctx_id: u32, commands: &mut [u8]) -> GpuResponse {
+    fn submit_command(&mut self, ctx_id: u32, commands: &mut [u8]) -> GpuResponse {
         match self.contexts.get_mut(&ctx_id) {
             Some(ctx) => match ctx.submit(&mut commands[..]) {
                 Ok(_) => GpuResponse::OkNoData,
@@ -816,28 +751,7 @@ impl Backend {
         }
     }
 
-    pub fn create_fence(&mut self, ctx_id: u32, fence_id: u32) -> GpuResponse {
-        // There is a mismatch of ordering that is intentional.
-        // This create_fence matches the other functions in Backend, yet
-        // the renderer matches the virgl interface.
-        match self.renderer.create_fence(fence_id, ctx_id) {
-            Ok(_) => GpuResponse::OkNoData,
-            Err(e) => {
-                error!("failed to create fence: {}", e);
-                GpuResponse::ErrUnspec
-            }
-        }
-    }
-
-    pub fn fence_poll(&mut self) -> u32 {
-        self.renderer.poll()
-    }
-
-    pub fn force_ctx_0(&mut self) {
-        self.renderer.force_ctx_0();
-    }
-
-    pub fn allocation_metadata(
+    fn allocation_metadata(
         &mut self,
         request_id: u32,
         request: Vec<u8>,
@@ -861,7 +775,7 @@ impl Backend {
         }
     }
 
-    pub fn resource_create_v2(
+    fn resource_create_v2(
         &mut self,
         resource_id: u32,
         guest_memory_type: u32,
@@ -911,9 +825,9 @@ impl Backend {
                             Ok(_resq) => match self.gpu_device_socket.recv() {
                                 Ok(response) => match response {
                                     VmMemoryResponse::RegisterMemory { pfn: _, slot } => {
-                                        entry.insert(VirtioGpuResource::v2_new(
-                                            self.display_width,
-                                            self.display_height,
+                                        entry.insert(Virtio3DResource::v2_new(
+                                            self.base.display_width,
+                                            self.base.display_height,
                                             slot,
                                             resource,
                                         ));
@@ -940,9 +854,9 @@ impl Backend {
                         }
                     }
                     _ => {
-                        entry.insert(VirtioGpuResource::new(
-                            self.display_width,
-                            self.display_height,
+                        entry.insert(Virtio3DResource::new(
+                            self.base.display_width,
+                            self.base.display_height,
                             resource,
                         ));
 
@@ -954,7 +868,7 @@ impl Backend {
         }
     }
 
-    pub fn resource_v2_unref(&mut self, resource_id: u32) -> GpuResponse {
+    fn resource_v2_unref(&mut self, resource_id: u32) -> GpuResponse {
         match self.resources.remove(&resource_id) {
             Some(entry) => match entry.kvm_slot {
                 Some(kvm_slot) => {
diff --git a/devices/src/virtio/gpu/virtio_backend.rs b/devices/src/virtio/gpu/virtio_backend.rs
new file mode 100644
index 0000000..605cf5a
--- /dev/null
+++ b/devices/src/virtio/gpu/virtio_backend.rs
@@ -0,0 +1,281 @@
+// Copyright 2020 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.
+
+use std::cell::RefCell;
+use std::collections::BTreeMap as Map;
+use std::num::NonZeroU32;
+use std::rc::Rc;
+
+use super::protocol::GpuResponse;
+use data_model::*;
+use gpu_display::*;
+use sys_util::{error, GuestMemory};
+
+pub trait VirtioResource {
+    fn width(&self) -> u32;
+
+    fn height(&self) -> u32;
+
+    fn import_to_display(&mut self, display: &Rc<RefCell<GpuDisplay>>) -> Option<u32>;
+
+    /// Performs a transfer to the given resource in the host from its backing in guest memory.
+    fn write_from_guest_memory(
+        &mut self,
+        x: u32,
+        y: u32,
+        width: u32,
+        height: u32,
+        src_offset: u64,
+        _mem: &GuestMemory,
+    );
+
+    /// Reads from this resource in the host to a volatile slice of memory.
+    fn read_to_volatile(
+        &mut self,
+        x: u32,
+        y: u32,
+        width: u32,
+        height: u32,
+        dst: VolatileSlice,
+        dst_stride: u32,
+    );
+}
+
+/// Handles some of the common functionality across the virtio 2D and 3D backends.
+pub struct VirtioBackend {
+    pub display: Rc<RefCell<GpuDisplay>>,
+    pub display_width: u32,
+    pub display_height: u32,
+    pub scanout_resource_id: Option<NonZeroU32>,
+    pub scanout_surface_id: Option<u32>,
+    pub cursor_resource_id: Option<NonZeroU32>,
+    pub cursor_surface_id: Option<u32>,
+    // Maps event devices to scanout number.
+    pub event_devices: Map<u32, u32>,
+}
+
+impl VirtioBackend {
+    pub fn import_event_device(&mut self, event_device: EventDevice, scanout: u32) {
+        // TODO(zachr): support more than one scanout.
+        if scanout != 0 {
+            return;
+        }
+
+        let mut display = self.display.borrow_mut();
+        let event_device_id = match display.import_event_device(event_device) {
+            Ok(id) => id,
+            Err(e) => {
+                error!("error importing event device: {}", e);
+                return;
+            }
+        };
+        self.scanout_surface_id
+            .map(|s| display.attach_event_device(s, event_device_id));
+        self.event_devices.insert(event_device_id, scanout);
+    }
+
+    /// Gets the list of supported display resolutions as a slice of `(width, height)` tuples.
+    pub fn display_info(&self) -> [(u32, u32); 1] {
+        [(self.display_width, self.display_height)]
+    }
+
+    /// Processes the internal `display` events and returns `true` if the main display was closed.
+    pub fn process_display(&mut self) -> bool {
+        let mut display = self.display.borrow_mut();
+        display.dispatch_events();
+        self.scanout_surface_id
+            .map(|s| display.close_requested(s))
+            .unwrap_or(false)
+    }
+
+    /// Sets the given resource id as the source of scanout to the display.
+    pub fn set_scanout(&mut self, resource_id: u32) -> GpuResponse {
+        let mut display = self.display.borrow_mut();
+        if resource_id == 0 {
+            if let Some(surface_id) = self.scanout_surface_id.take() {
+                display.release_surface(surface_id);
+            }
+            self.scanout_resource_id = None;
+            GpuResponse::OkNoData
+        } else {
+            self.scanout_resource_id = NonZeroU32::new(resource_id);
+
+            if self.scanout_surface_id.is_none() {
+                match display.create_surface(None, self.display_width, self.display_height) {
+                    Ok(surface_id) => {
+                        self.scanout_surface_id = Some(surface_id);
+                    }
+                    Err(e) => error!("failed to create display surface: {}", e),
+                }
+            }
+            GpuResponse::OkNoData
+        }
+    }
+
+    pub fn flush_resource(
+        &mut self,
+        resource: &mut dyn VirtioResource,
+        resource_id: u32,
+    ) -> GpuResponse {
+        let mut response = GpuResponse::OkNoData;
+
+        if let Some(scannout_resource_id) = self.scanout_resource_id {
+            if scannout_resource_id.get() == resource_id {
+                response = self.flush_scannout_resource_to_surface(resource);
+            }
+        }
+
+        if response != GpuResponse::OkNoData {
+            return response;
+        }
+
+        if let Some(cursor_resource_id) = self.cursor_resource_id {
+            if cursor_resource_id.get() == resource_id {
+                response = self.flush_cursor_resource_to_surface(resource);
+            }
+        }
+
+        response
+    }
+
+    pub fn flush_scannout_resource_to_surface(
+        &mut self,
+        resource: &mut dyn VirtioResource,
+    ) -> GpuResponse {
+        match self.scanout_surface_id {
+            Some(surface_id) => self.flush_resource_to_surface(resource, surface_id),
+            None => GpuResponse::OkNoData,
+        }
+    }
+
+    pub fn flush_cursor_resource_to_surface(
+        &mut self,
+        resource: &mut dyn VirtioResource,
+    ) -> GpuResponse {
+        match self.cursor_surface_id {
+            Some(surface_id) => self.flush_resource_to_surface(resource, surface_id),
+            None => GpuResponse::OkNoData,
+        }
+    }
+
+    pub fn flush_resource_to_surface(
+        &mut self,
+        resource: &mut dyn VirtioResource,
+        surface_id: u32,
+    ) -> GpuResponse {
+        if let Some(import_id) = resource.import_to_display(&self.display) {
+            self.display.borrow_mut().flip_to(surface_id, import_id);
+            return GpuResponse::OkNoData;
+        }
+
+        // Import failed, fall back to a copy.
+        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_region(
+            surface_id,
+            0,
+            0,
+            self.display_width,
+            self.display_height,
+        ) {
+            Some(fb) => fb,
+            None => {
+                error!("failed to access framebuffer for surface {}", surface_id);
+                return GpuResponse::ErrUnspec;
+            }
+        };
+
+        resource.read_to_volatile(
+            0,
+            0,
+            self.display_width,
+            self.display_height,
+            fb.as_volatile_slice(),
+            fb.stride(),
+        );
+
+        display.flip(surface_id);
+
+        GpuResponse::OkNoData
+    }
+
+    /// Updates the cursor's memory to the given id, and sets its position to the given coordinates.
+    pub fn update_cursor(
+        &mut self,
+        id: u32,
+        x: u32,
+        y: u32,
+        resource: Option<&mut dyn VirtioResource>,
+    ) -> GpuResponse {
+        if id == 0 {
+            if let Some(surface_id) = self.cursor_surface_id.take() {
+                self.display.borrow_mut().release_surface(surface_id);
+            }
+            self.cursor_resource_id = None;
+            GpuResponse::OkNoData
+        } else if let Some(resource) = resource {
+            self.cursor_resource_id = NonZeroU32::new(id);
+
+            if self.cursor_surface_id.is_none() {
+                match self.display.borrow_mut().create_surface(
+                    self.scanout_surface_id,
+                    resource.width(),
+                    resource.height(),
+                ) {
+                    Ok(surface_id) => self.cursor_surface_id = Some(surface_id),
+                    Err(e) => {
+                        error!("failed to create cursor surface: {}", e);
+                        return GpuResponse::ErrUnspec;
+                    }
+                }
+            }
+
+            let cursor_surface_id = self.cursor_surface_id.unwrap();
+            self.display
+                .borrow_mut()
+                .set_position(cursor_surface_id, x, y);
+
+            // Gets the resource's pixels into the display by importing the buffer.
+            if let Some(import_id) = resource.import_to_display(&self.display) {
+                self.display
+                    .borrow_mut()
+                    .flip_to(cursor_surface_id, import_id);
+                return GpuResponse::OkNoData;
+            }
+
+            // Importing failed, so try copying the pixels into the surface's slower shared memory
+            // framebuffer.
+            if let Some(fb) = self.display.borrow_mut().framebuffer(cursor_surface_id) {
+                resource.read_to_volatile(
+                    0,
+                    0,
+                    resource.width(),
+                    resource.height(),
+                    fb.as_volatile_slice(),
+                    fb.stride(),
+                )
+            }
+            self.display.borrow_mut().flip(cursor_surface_id);
+            GpuResponse::OkNoData
+        } else {
+            GpuResponse::ErrInvalidResourceId
+        }
+    }
+
+    /// Moves the cursor's position to the given coordinates.
+    pub fn move_cursor(&mut self, x: u32, y: u32) -> GpuResponse {
+        if let Some(cursor_surface_id) = self.cursor_surface_id {
+            if let Some(scanout_surface_id) = self.scanout_surface_id {
+                let mut display = self.display.borrow_mut();
+                display.set_position(cursor_surface_id, x, y);
+                display.commit(scanout_surface_id);
+            }
+        }
+        GpuResponse::OkNoData
+    }
+}
diff --git a/gpu_display/src/gpu_display_wl.rs b/gpu_display/src/gpu_display_wl.rs
index 04ddb54..b96b8ef 100644
--- a/gpu_display/src/gpu_display_wl.rs
+++ b/gpu_display/src/gpu_display_wl.rs
@@ -348,7 +348,7 @@ impl DisplayT for DisplayWl {
     fn release_event_device(&mut self, _event_device_id: u32) {
         // unsupported
     }
-    fn attach_event_device(&mut self, surface_id: u32, event_device_id: u32) {
+    fn attach_event_device(&mut self, _surface_id: u32, _event_device_id: u32) {
         // unsupported
     }
 }
diff --git a/src/main.rs b/src/main.rs
index e053082..fb1be25 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -22,7 +22,7 @@ use crosvm::{
     linux, BindMount, Config, DiskOption, Executable, GidMap, SharedDir, TouchDeviceOption,
 };
 #[cfg(feature = "gpu")]
-use devices::virtio::gpu::{GpuParameters, DEFAULT_GPU_PARAMS};
+use devices::virtio::gpu::{GpuMode, GpuParameters, DEFAULT_GPU_PARAMS};
 use devices::{SerialParameters, SerialType};
 use disk::QcowFile;
 use msg_socket::{MsgReceiver, MsgSender, MsgSocket};
@@ -124,6 +124,12 @@ fn parse_gpu_options(s: Option<&str>) -> argument::Result<GpuParameters> {
 
         for (k, v) in opts {
             match k {
+                "2d" | "2D" => {
+                    gpu_params.mode = GpuMode::Mode2D;
+                }
+                "3d" | "3D" => {
+                    gpu_params.mode = GpuMode::Mode3D;
+                }
                 "egl" => match v {
                     "true" | "" => {
                         gpu_params.renderer_use_egl = true;