summary refs log tree commit diff
path: root/devices/src
diff options
context:
space:
mode:
Diffstat (limited to 'devices/src')
-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
4 files changed, 1461 insertions, 385 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
+    }
+}