summary refs log tree commit diff
path: root/devices/src/virtio/gpu/backend.rs
diff options
context:
space:
mode:
authorZach Reizner <zachr@google.com>2017-09-13 19:15:43 -0700
committerchrome-bot <chrome-bot@chromium.org>2018-07-20 05:30:54 -0700
commit3a8100adc75d805300f23f7cff25e3e1d6b40b33 (patch)
tree08efcf2c527b429836d42d431ec84e7d096a94b0 /devices/src/virtio/gpu/backend.rs
parentf40bb190ece97c908f8dba2efc7c1aceb4fc0e0b (diff)
downloadcrosvm-3a8100adc75d805300f23f7cff25e3e1d6b40b33.tar
crosvm-3a8100adc75d805300f23f7cff25e3e1d6b40b33.tar.gz
crosvm-3a8100adc75d805300f23f7cff25e3e1d6b40b33.tar.bz2
crosvm-3a8100adc75d805300f23f7cff25e3e1d6b40b33.tar.lz
crosvm-3a8100adc75d805300f23f7cff25e3e1d6b40b33.tar.xz
crosvm-3a8100adc75d805300f23f7cff25e3e1d6b40b33.tar.zst
crosvm-3a8100adc75d805300f23f7cff25e3e1d6b40b33.zip
gpu: implement virtio-gpu
Basic 2D and 3D support is there. The drm_cursor_test and
null_platform_test in drm-tests should run to completion.

The extra device is hidden behind both a build time feature called 'gpu'
and the device is only added to a VM if the  '--gpu' flag is given.

TEST=build with --features=gpu;
     drm_cursor_test && null_platform_test
BUG=chromium:837073

Change-Id: Ic91acaaebbee395599d7e1ba41c24c9ed2d84169
Reviewed-on: https://chromium-review.googlesource.com/1036862
Commit-Ready: Zach Reizner <zachr@chromium.org>
Tested-by: Zach Reizner <zachr@chromium.org>
Reviewed-by: Zach Reizner <zachr@chromium.org>
Diffstat (limited to 'devices/src/virtio/gpu/backend.rs')
-rw-r--r--devices/src/virtio/gpu/backend.rs817
1 files changed, 817 insertions, 0 deletions
diff --git a/devices/src/virtio/gpu/backend.rs b/devices/src/virtio/gpu/backend.rs
new file mode 100644
index 0000000..50fd91c
--- /dev/null
+++ b/devices/src/virtio/gpu/backend.rs
@@ -0,0 +1,817 @@
+// Copyright 2018 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 for the transport agnostic virtio-gpu protocol, including display and rendering.
+
+use std::cell::RefCell;
+use std::collections::btree_map::Entry;
+use std::collections::BTreeMap as Map;
+use std::os::unix::io::AsRawFd;
+use std::rc::Rc;
+
+use data_model::*;
+
+use sys_util::{GuestAddress, GuestMemory};
+
+use super::gpu_buffer::{Device, Buffer, Format, Flags};
+use super::gpu_display::*;
+use super::gpu_renderer::{Box3, Renderer, Context as RendererContext,
+                          Resource as GpuRendererResource, ResourceCreateArgs};
+
+use super::protocol::GpuResponse;
+
+const DEFAULT_WIDTH: u32 = 1280;
+const DEFAULT_HEIGHT: u32 = 1024;
+
+/// Trait for virtio-gpu resources allocated by the guest.
+trait VirglResource {
+    /// The width in pixels of this resource.
+    fn width(&self) -> u32;
+
+    /// The height in pixels of this resource.
+    fn height(&self) -> u32;
+
+    /// Associates the backing for this resource with the given guest memory.
+    fn attach_guest_backing(&mut self, mem: &GuestMemory, vecs: Vec<(GuestAddress, usize)>);
+
+    /// Removes associated memory for this resource previously made with `attach_guest_backing`.
+    fn detach_guest_backing(&mut self);
+
+    /// Returns the GPU `Buffer` for this resource, if it has one.
+    fn buffer(&self) -> Option<&Buffer> {
+        None
+    }
+
+    /// Returns the renderer's concrete `GpuRendererResource` for this resource, if it has one.
+    fn gpu_renderer_resource(&mut self) -> Option<&mut GpuRendererResource> {
+        None
+    }
+
+    /// Returns an import ID for this resource onto the given display, if successful.
+    fn import_to_display(&mut self, _display: &Rc<RefCell<GpuDisplay>>) -> Option<u32> {
+        None
+    }
+
+    /// Copies the given rectangle of pixels from guest memory, using the backing specified from a
+    /// call to `attach_guest_backing`.
+    fn write_from_guest_memory(&mut self,
+                               x: u32,
+                               y: u32,
+                               width: u32,
+                               height: u32,
+                               mem: &GuestMemory);
+
+    /// Reads from the given rectangle of pixels in the resource to the `dst` slice of memory.
+    fn read_to_volatile(&mut self, x: u32, y: u32, width: u32, height: u32, dst: VolatileSlice);
+}
+
+impl VirglResource for GpuRendererResource {
+    fn width(&self) -> u32 {
+        match self.get_info() {
+            Ok(info) => info.width,
+            Err(_) => 0,
+        }
+    }
+    fn height(&self) -> u32 {
+        match self.get_info() {
+            Ok(info) => info.height,
+            Err(_) => 0,
+        }
+    }
+
+    fn attach_guest_backing(&mut self, mem: &GuestMemory, vecs: Vec<(GuestAddress, usize)>) {
+        if let Err(e) = self.attach_backing(&vecs[..], mem) {
+            error!("failed to attach backing to resource: {}", e);
+        }
+    }
+
+    fn detach_guest_backing(&mut self) {
+        self.detach_backing();
+    }
+
+    fn gpu_renderer_resource(&mut self) -> Option<&mut GpuRendererResource> {
+        Some(self)
+    }
+
+    fn write_from_guest_memory(&mut self,
+                               x: u32,
+                               y: u32,
+                               width: u32,
+                               height: u32,
+                               _mem: &GuestMemory) {
+        let res = self.transfer_write(None,
+                                      0,
+                                      0,
+                                      0,
+                                      Box3 {
+                                          x,
+                                          y,
+                                          z: 0,
+                                          w: width,
+                                          h: height,
+                                          d: 0,
+                                      },
+                                      0);
+        if let Err(e) = res {
+            error!("failed to write to resource (x={} y={} w={} h={}): {}",
+                   x,
+                   y,
+                   width,
+                   height,
+                   e);
+        }
+    }
+
+    fn read_to_volatile(&mut self, x: u32, y: u32, width: u32, height: u32, dst: VolatileSlice) {
+        let res = GpuRendererResource::read_to_volatile(self,
+                                                        None,
+                                                        0,
+                                                        0,
+                                                        0,
+                                                        Box3 {
+                                                            x,
+                                                            y,
+                                                            z: 0,
+                                                            w: width,
+                                                            h: height,
+                                                            d: 0,
+                                                        },
+                                                        0,
+                                                        dst);
+        if let Err(e) = res {
+            error!("failed to read from resource: {}", e);
+        }
+    }
+}
+
+/// A buffer backed with a `gpu_buffer::Buffer`.
+struct BackedBuffer {
+    display_import: Option<(Rc<RefCell<GpuDisplay>>, u32)>,
+    backing: Vec<(GuestAddress, usize)>,
+    buffer: Buffer,
+}
+
+impl From<Buffer> for BackedBuffer {
+    fn from(buffer: Buffer) -> BackedBuffer {
+        BackedBuffer {
+            display_import: None,
+            backing: Vec::new(),
+            buffer,
+        }
+    }
+}
+
+impl VirglResource for BackedBuffer {
+    fn width(&self) -> u32 {
+        self.buffer.width()
+    }
+
+    fn height(&self) -> u32 {
+        self.buffer.height()
+    }
+
+    fn attach_guest_backing(&mut self, _mem: &GuestMemory, vecs: Vec<(GuestAddress, usize)>) {
+        self.backing = vecs;
+    }
+
+    fn detach_guest_backing(&mut self) {
+        self.backing.clear()
+    }
+
+    fn buffer(&self) -> Option<&Buffer> {
+        Some(&self.buffer)
+    }
+
+    fn import_to_display(&mut self, display: &Rc<RefCell<GpuDisplay>>) -> Option<u32> {
+        if let Some((ref self_display, import)) = self.display_import {
+            if Rc::ptr_eq(&self_display, display) {
+                return Some(import);
+            }
+        }
+        let dmabuf = match self.buffer.export_plane_fd(0) {
+            Ok(dmabuf) => dmabuf,
+            Err(e) => {
+                error!("failed to get dmabuf for scanout: {}", e);
+                return None;
+            }
+        };
+
+        match display
+                  .borrow_mut()
+                  .import_dmabuf(dmabuf.as_raw_fd(),
+                                 0, /* offset */
+                                 self.buffer.stride(),
+                                 self.buffer.format_modifier(),
+                                 self.buffer.width(),
+                                 self.buffer.height(),
+                                 self.buffer.format().into()) {
+            Ok(import_id) => {
+                self.display_import = Some((display.clone(), import_id));
+                Some(import_id)
+            }
+            Err(e) => {
+                error!("failed to import dmabuf for display: {:?}", e);
+                None
+            }
+        }
+    }
+
+    fn write_from_guest_memory(&mut self,
+                               x: u32,
+                               y: u32,
+                               width: u32,
+                               height: u32,
+                               mem: &GuestMemory) {
+        let res = self.buffer
+            .write_from_sg(x,
+                           y,
+                           width,
+                           height,
+                           0,
+                           self.backing
+                               .iter()
+                               .map(|&(addr, len)| {
+                                        mem.get_slice(addr.offset(), len as u64).unwrap()
+                                    }));
+        if let Err(e) = res {
+            error!("failed to write to resource from guest memory: {:?}", e)
+        }
+    }
+
+    fn read_to_volatile(&mut self, x: u32, y: u32, width: u32, height: u32, dst: VolatileSlice) {
+        if let Err(e) = self.buffer.read_to_volatile(x, y, width, height, 0, dst) {
+            error!("failed to copy resource: {:?}", e);
+        }
+    }
+}
+
+/// 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>>,
+    device: Device,
+    renderer: Renderer,
+    resources: Map<u32, Box<VirglResource>>,
+    contexts: Map<u32, RendererContext>,
+    scanout_surface: Option<u32>,
+    cursor_surface: Option<u32>,
+    scanout_resource: u32,
+    cursor_resource: u32,
+}
+
+impl Backend {
+    /// Creates a new backend for virtio-gpu that realizes all commands using the given `device` for
+    /// allocating buffers, `display` for showing the results, and `renderer` for submitting
+    /// rendering commands.
+    pub fn new(device: Device, display: GpuDisplay, renderer: Renderer) -> Backend {
+        Backend {
+            display: Rc::new(RefCell::new(display)),
+            device,
+            renderer,
+            resources: Default::default(),
+            contexts: Default::default(),
+            scanout_surface: None,
+            cursor_surface: None,
+            scanout_resource: 0,
+            cursor_resource: 0,
+        }
+    }
+
+    /// Gets a reference to the display passed into `new`.
+    pub fn display(&self) -> &Rc<RefCell<GpuDisplay>> {
+        &self.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)
+    }
+
+    /// Gets the list of supported display resolutions as a slice of `(width, height)` tuples.
+    pub fn display_info(&self) -> &[(u32, u32)] {
+        &[(DEFAULT_WIDTH, DEFAULT_HEIGHT)]
+    }
+
+    /// 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,
+                              fourcc: u32)
+                              -> GpuResponse {
+        if id == 0 {
+            return GpuResponse::ErrInvalidResourceId;
+        }
+        match self.resources.entry(id) {
+            Entry::Vacant(slot) => {
+                let res = self.device
+                    .create_buffer(width,
+                                   height,
+                                   Format::from(fourcc),
+                                   Flags::empty().use_scanout(true).use_linear(true));
+                match res {
+                    Ok(res) => {
+                        slot.insert(Box::from(BackedBuffer::from(res)));
+                        GpuResponse::OkNoData
+                    }
+                    Err(_) => {
+                        error!("failed to create renderer resource {}", fourcc);
+                        GpuResponse::ErrUnspec
+                    }
+                }
+            }
+            Entry::Occupied(_) => GpuResponse::ErrInvalidResourceId,
+        }
+    }
+
+    /// Removes the guest's reference count for the given resource id.
+    pub 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.
+    pub fn set_scanout(&mut self, id: u32) -> GpuResponse {
+        let mut display = self.display.borrow_mut();
+        if 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(&id).is_some() {
+            self.scanout_resource = id;
+
+            if self.scanout_surface.is_none() {
+                match display.create_surface(None, DEFAULT_WIDTH, DEFAULT_HEIGHT) {
+                    Ok(surface) => self.scanout_surface = Some(surface),
+                    Err(e) => error!("failed to create display surface: {:?}", e),
+                }
+            }
+            GpuResponse::OkNoData
+        } else {
+            GpuResponse::ErrInvalidResourceId
+        }
+    }
+
+    fn flush_resource_to_surface(&mut self,
+                                 resource_id: u32,
+                                 surface_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 display = self.display.borrow_mut();
+        // Prevent overwriting a buffer that is currently being used by the compositor.
+        if display.next_buffer_in_use(surface_id) {
+            return GpuResponse::OkNoData;
+        }
+        let fb = match display.framebuffer_memory(surface_id) {
+            Some(fb) => fb,
+            None => {
+                error!("failed to access framebuffer for surface {}", surface_id);
+                return GpuResponse::ErrUnspec;
+            }
+        };
+
+        resource.read_to_volatile(x, y, width, height, fb);
+        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);
+            }
+        }
+
+        return response;
+    }
+
+    /// Copes the given rectangle of pixels of the given resource's backing memory to the host side
+    /// resource.
+    pub fn transfer_to_resource_2d(&mut self,
+                                   id: u32,
+                                   x: u32,
+                                   y: u32,
+                                   width: u32,
+                                   height: u32,
+                                   mem: &GuestMemory)
+                                   -> GpuResponse {
+        match self.resources.get_mut(&id) {
+            Some(res) => {
+                res.write_from_guest_memory(x, y, width, height, mem);
+                GpuResponse::OkNoData
+            }
+            None => GpuResponse::ErrInvalidResourceId,
+        }
+    }
+
+    /// 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(&mut self,
+                          id: u32,
+                          mem: &GuestMemory,
+                          vecs: Vec<(GuestAddress, usize)>)
+                          -> GpuResponse {
+        match self.resources.get_mut(&id) {
+            Some(resource) => {
+                resource.attach_guest_backing(mem, vecs);
+                GpuResponse::OkNoData
+            }
+            None => GpuResponse::ErrInvalidResourceId,
+        }
+    }
+
+    /// Detaches any backing memory from the given resource, if there is any.
+    pub fn detach_backing(&mut self, id: u32) -> GpuResponse {
+        match self.resources.get_mut(&id) {
+            Some(resource) => {
+                resource.detach_guest_backing();
+                GpuResponse::OkNoData
+            }
+            None => GpuResponse::ErrInvalidResourceId,
+        }
+    }
+
+    /// 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;
+            }
+
+            // Importing failed, so try copying the pixels into the surface's slower shared memory
+            // framebuffer.
+            if let Some(buffer) = resource.buffer() {
+                if let Some(fb) = self.display
+                       .borrow_mut()
+                       .framebuffer_memory(cursor_surface) {
+                    if let Err(e) = buffer.read_to_volatile(0,
+                                                            0,
+                                                            buffer.width(),
+                                                            buffer.height(),
+                                                            0,
+                                                            fb) {
+                        error!("failed to copy resource to cursor: {:?}", e);
+                        return GpuResponse::ErrInvalidParameter;
+                    }
+                }
+                self.display.borrow_mut().flip(cursor_surface);
+            }
+            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) = self.cursor_surface {
+            if let Some(scanout_surface) = self.scanout_surface {
+                let display = self.display.borrow_mut();
+                display.set_position(cursor_surface, x, y);
+                display.commit(scanout_surface);
+            }
+        }
+        GpuResponse::OkNoData
+    }
+
+    /// Gets the renderer's capset information associated with `index`.
+    pub fn get_capset_info(&self, index: u32) -> GpuResponse {
+        match index {
+            0 => {
+                let id = 1; // VIRTIO_GPU_CAPSET_VIRGL
+                let (version, size) = self.renderer.get_cap_set_info(id);
+                GpuResponse::OkCapsetInfo { id, version, size }
+            }
+            _ => GpuResponse::ErrInvalidParameter,
+        }
+    }
+
+    /// Gets the capset of `version` associated with `id`.
+    pub 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 {
+        if id == 0 {
+            return GpuResponse::ErrInvalidContextId;
+        }
+        match self.contexts.entry(id) {
+            Entry::Occupied(_) => GpuResponse::ErrInvalidContextId,
+            Entry::Vacant(slot) => {
+                match self.renderer.create_context(id) {
+                    Ok(ctx) => {
+                        slot.insert(ctx);
+                        GpuResponse::OkNoData
+                    }
+                    Err(e) => {
+                        error!("failed to create renderer ctx: {}", e);
+                        GpuResponse::ErrUnspec
+                    }
+                }
+            }
+        }
+    }
+
+    /// Destorys the renderer context associated with `id`.
+    pub fn destroy_renderer_context(&mut self, id: u32) -> GpuResponse {
+        match self.contexts.remove(&id) {
+            Some(_) => GpuResponse::OkNoData,
+            None => GpuResponse::ErrInvalidContextId,
+        }
+    }
+
+    /// Attaches the indicated resource to the given context.
+    pub 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)
+                   .and_then(|res| res.gpu_renderer_resource())) {
+            (Some(ctx), Some(res)) => {
+                ctx.attach(res);
+                GpuResponse::OkNoData
+            }
+            (None, _) => GpuResponse::ErrInvalidContextId,
+            (_, None) => GpuResponse::ErrInvalidResourceId,
+        }
+    }
+
+    /// detaches the indicated resource to the given context.
+    pub 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)
+                   .and_then(|res| res.gpu_renderer_resource())) {
+            (Some(ctx), Some(res)) => {
+                ctx.detach(res);
+                GpuResponse::OkNoData
+            }
+            (None, _) => GpuResponse::ErrInvalidContextId,
+            (_, None) => GpuResponse::ErrInvalidResourceId,
+        }
+    }
+
+    /// Creates a 3D resource with the given properties and associated it with the given id.
+    pub 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 {
+        if id == 0 {
+            return GpuResponse::ErrInvalidResourceId;
+        }
+        match self.resources.entry(id) {
+            Entry::Occupied(_) => GpuResponse::ErrInvalidResourceId,
+            Entry::Vacant(slot) => {
+                let res = self.renderer
+                    .create_resource(ResourceCreateArgs {
+                                         handle: id,
+                                         target,
+                                         format,
+                                         bind,
+                                         width,
+                                         height,
+                                         depth,
+                                         array_size,
+                                         last_level,
+                                         nr_samples,
+                                         flags,
+                                     });
+                match res {
+                    Ok(res) => {
+                        slot.insert(Box::new(res));
+                        GpuResponse::OkNoData
+                    }
+                    Err(e) => {
+                        error!("failed to create renderer resource: {}", e);
+                        GpuResponse::ErrUnspec
+                    }
+                }
+            }
+        }
+    }
+
+    /// 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(&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 {
+        let ctx = match ctx_id {
+            0 => None,
+            id => {
+                match self.contexts.get(&id) {
+                    None => return GpuResponse::ErrInvalidContextId,
+                    ctx => ctx,
+                }
+            }
+        };
+        match self.resources.get_mut(&res_id) {
+            Some(res) => {
+                match res.gpu_renderer_resource() {
+                    Some(res) => {
+                        let transfer_box = Box3 {
+                            x,
+                            y,
+                            z,
+                            w: width,
+                            h: height,
+                            d: depth,
+                        };
+                        let res = res.transfer_write(ctx,
+                                                     level,
+                                                     stride,
+                                                     layer_stride,
+                                                     transfer_box,
+                                                     offset);
+                        match res {
+                            Ok(_) => GpuResponse::OkNoData,
+                            Err(e) => {
+                                error!("failed to transfer to host: {}", e);
+                                GpuResponse::ErrUnspec
+                            }
+                        }
+                    }
+                    None => GpuResponse::ErrInvalidResourceId,
+                }
+            }
+            None => GpuResponse::ErrInvalidResourceId,
+        }
+    }
+
+    /// Copes the given rectangle of pixels from the resource to the given resource's backing
+    /// memory.
+    pub 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 {
+        let ctx = match ctx_id {
+            0 => None,
+            id => {
+                match self.contexts.get(&id) {
+                    None => return GpuResponse::ErrInvalidContextId,
+                    ctx => ctx,
+                }
+            }
+        };
+        match self.resources.get_mut(&res_id) {
+            Some(res) => {
+                match res.gpu_renderer_resource() {
+                    Some(res) => {
+                        let transfer_box = Box3 {
+                            x,
+                            y,
+                            z,
+                            w: width,
+                            h: height,
+                            d: depth,
+                        };
+                        let res = res.transfer_read(ctx,
+                                                    level,
+                                                    stride,
+                                                    layer_stride,
+                                                    transfer_box,
+                                                    offset);
+                        match res {
+                            Ok(_) => GpuResponse::OkNoData,
+                            Err(e) => {
+                                error!("failed to transfer from host: {}", e);
+                                GpuResponse::ErrUnspec
+                            }
+                        }
+                    }
+                    None => GpuResponse::ErrInvalidResourceId,
+                }
+            }
+            None => GpuResponse::ErrInvalidResourceId,
+        }
+    }
+
+    /// Submits a command buffer to the given rendering context.
+    pub 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,
+                    Err(e) => {
+                        error!("failed to submit command buffer: {}", e);
+                        GpuResponse::ErrUnspec
+                    }
+                }
+            }
+            None => GpuResponse::ErrInvalidContextId,
+        }
+    }
+}