summary refs log tree commit diff
diff options
context:
space:
mode:
authorLingfeng Yang <lfy@google.com>2020-01-30 10:00:36 -0800
committerCommit Bot <commit-bot@chromium.org>2020-02-19 06:18:37 +0000
commitddbe8b7e8eb141bf7ccbb0554278fff7164be166 (patch)
treee1673e3a329e8f7472f40492d7abfe07831e6a61
parent79f10170da3c131ee23eee3018f981811e09a4cc (diff)
downloadcrosvm-ddbe8b7e8eb141bf7ccbb0554278fff7164be166.tar
crosvm-ddbe8b7e8eb141bf7ccbb0554278fff7164be166.tar.gz
crosvm-ddbe8b7e8eb141bf7ccbb0554278fff7164be166.tar.bz2
crosvm-ddbe8b7e8eb141bf7ccbb0554278fff7164be166.tar.lz
crosvm-ddbe8b7e8eb141bf7ccbb0554278fff7164be166.tar.xz
crosvm-ddbe8b7e8eb141bf7ccbb0554278fff7164be166.tar.zst
crosvm-ddbe8b7e8eb141bf7ccbb0554278fff7164be166.zip
virtio-gpu: gfxstream backend
Adds a new backend type, gfxstream, that calls out to a C library
implementing the actual rendering.

The purpose is to allow the Cuttlefish and Android Studio Emulator teams
to use crosvm with the current API-forwarding style of rendering
employed in the Android Studio Emulator.

Also, introduces a new key to the --gpu command line interface,
backend=, which selects from different backends. Note that the previous
behavior is now deprecated and will be removed after some time
(when all clients switch over to backend=).

The gfxstream backend itself implements a subset of 3d-related resource
and context creation/transfer/execbuffer commands. Their meaning is
specific to the way in which they are interpreted in the backend
library.

To interface with display, gfx stream backend takes a callback that is
run on guest vsync. The callback is responsible for repainting the
display's contents. gfx stream provides a callback, get_pixels, that can
be used asynchronously. The asyncness is not taken advantage of
currently but will be useful for cases where the client attached to the
VMM might want to update at a different rate than guest vsync.

The guts of the stream backend library is currently defined here:

https://android.googlesource.com/platform/external/qemu/+/refs/heads/emu-master-dev/android-qemu2-glue/emulation/virtio-goldfish-pipe.cpp

The linking of the library is controlled via the feature "gfxstream".
If the feature is turned off, we use a default do-nothing stub impl.

Next steps:
- integrate virtio-gpu-next so as to have host coherent memory for
vulkan
- Figure out low latency command submit/response with SUBMIT_CMD_3DV2

BUG=b:146066070

Change-Id: If647381c15e5459cec85e2325f97e2f0a963b083
Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/platform/crosvm/+/2033305
Tested-by: kokoro <noreply+kokoro@google.com>
Tested-by: Lingfeng Yang <lfy@google.com>
Reviewed-by: Lingfeng Yang <lfy@google.com>
Reviewed-by: Jason Macnak <natsu@google.com>
Reviewed-by: Zach Reizner <zachr@chromium.org>
Auto-Submit: Lingfeng Yang <lfy@google.com>
Commit-Queue: Zach Reizner <zachr@chromium.org>
-rw-r--r--Cargo.toml1
-rw-r--r--devices/Cargo.toml1
-rw-r--r--devices/src/virtio/gpu/mod.rs23
-rw-r--r--devices/src/virtio/gpu/virtio_gfxstream_backend.rs722
-rw-r--r--src/main.rs26
5 files changed, 773 insertions, 0 deletions
diff --git a/Cargo.toml b/Cargo.toml
index 9afef46..7eb7215 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -38,6 +38,7 @@ wl-dmabuf = ["devices/wl-dmabuf", "gpu_buffer", "resources/wl-dmabuf"]
 x = ["devices/x"]
 virtio-gpu-next = ["gpu_renderer/virtio-gpu-next"]
 composite-disk = ["protos/composite-disk", "protobuf", "disk/composite-disk"]
+gfxstream = ["devices/gfxstream"]
 
 [dependencies]
 arch = { path = "arch" }
diff --git a/devices/Cargo.toml b/devices/Cargo.toml
index 236cf4e..83aa406 100644
--- a/devices/Cargo.toml
+++ b/devices/Cargo.toml
@@ -9,6 +9,7 @@ gpu = ["gpu_buffer", "gpu_display", "gpu_renderer"]
 tpm = ["protos/trunks", "tpm2"]
 wl-dmabuf = []
 x = ["gpu_display/x"]
+gfxstream = ["gpu"]
 
 [dependencies]
 audio_streams = "*"
diff --git a/devices/src/virtio/gpu/mod.rs b/devices/src/virtio/gpu/mod.rs
index 49fdff4..0fcc453 100644
--- a/devices/src/virtio/gpu/mod.rs
+++ b/devices/src/virtio/gpu/mod.rs
@@ -6,6 +6,7 @@ mod protocol;
 mod virtio_2d_backend;
 mod virtio_3d_backend;
 mod virtio_backend;
+mod virtio_gfxstream_backend;
 
 use std::cell::RefCell;
 use std::collections::VecDeque;
@@ -40,6 +41,8 @@ use super::{PciCapabilityType, VirtioPciShmCap, VirtioPciShmCapID};
 use self::protocol::*;
 use self::virtio_2d_backend::Virtio2DBackend;
 use self::virtio_3d_backend::Virtio3DBackend;
+#[cfg(feature = "gfxstream")]
+use self::virtio_gfxstream_backend::VirtioGfxStreamBackend;
 use crate::pci::{PciBarConfiguration, PciBarPrefetchable, PciBarRegionType, PciCapability};
 
 use vm_control::VmMemoryControlRequestSocket;
@@ -51,6 +54,8 @@ pub const DEFAULT_DISPLAY_HEIGHT: u32 = 1024;
 pub enum GpuMode {
     Mode2D,
     Mode3D,
+    #[cfg(feature = "gfxstream")]
+    ModeGfxStream,
 }
 
 #[derive(Debug)]
@@ -305,6 +310,8 @@ trait Backend {
 enum BackendKind {
     Virtio2D,
     Virtio3D,
+    #[cfg(feature = "gfxstream")]
+    VirtioGfxStream,
 }
 
 impl BackendKind {
@@ -313,6 +320,8 @@ impl BackendKind {
         match self {
             BackendKind::Virtio2D => Virtio2DBackend::capsets(),
             BackendKind::Virtio3D => Virtio3DBackend::capsets(),
+            #[cfg(feature = "gfxstream")]
+            BackendKind::VirtioGfxStream => VirtioGfxStreamBackend::capsets(),
         }
     }
 
@@ -321,6 +330,8 @@ impl BackendKind {
         match self {
             BackendKind::Virtio2D => Virtio2DBackend::features(),
             BackendKind::Virtio3D => Virtio3DBackend::features(),
+            #[cfg(feature = "gfxstream")]
+            BackendKind::VirtioGfxStream => VirtioGfxStreamBackend::features(),
         }
     }
 
@@ -354,6 +365,16 @@ impl BackendKind {
                 gpu_device_socket,
                 pci_bar,
             ),
+            #[cfg(feature = "gfxstream")]
+            BackendKind::VirtioGfxStream => VirtioGfxStreamBackend::build(
+                possible_displays,
+                display_width,
+                display_height,
+                renderer_flags,
+                event_devices,
+                gpu_device_socket,
+                pci_bar,
+            ),
         }
     }
 }
@@ -1033,6 +1054,8 @@ impl Gpu {
         let backend_kind = match gpu_parameters.mode {
             GpuMode::Mode2D => BackendKind::Virtio2D,
             GpuMode::Mode3D => BackendKind::Virtio3D,
+            #[cfg(feature = "gfxstream")]
+            GpuMode::ModeGfxStream => BackendKind::VirtioGfxStream,
         };
 
         Gpu {
diff --git a/devices/src/virtio/gpu/virtio_gfxstream_backend.rs b/devices/src/virtio/gpu/virtio_gfxstream_backend.rs
new file mode 100644
index 0000000..aa02e15
--- /dev/null
+++ b/devices/src/virtio/gpu/virtio_gfxstream_backend.rs
@@ -0,0 +1,722 @@
+// 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 for
+//! API passthrough.
+
+#![cfg(feature = "gfxstream")]
+
+use std::cell::RefCell;
+use std::collections::btree_map::Entry;
+use std::collections::BTreeMap as Map;
+use std::fs::File;
+use std::mem::transmute;
+use std::os::raw::{c_char, c_int, c_uchar, c_uint, c_void};
+use std::panic;
+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, VIRTIO_GPU_F_VIRGL};
+
+// C definitions related to gfxstream
+// In gfxstream, only write_fence is used
+// (for synchronization of commands delivered)
+#[repr(C)]
+#[derive(Debug, Copy, Clone)]
+pub struct GfxStreamRendererCallbacks {
+    pub version: c_int,
+    pub write_fence: unsafe extern "C" fn(cookie: *mut c_void, fence: u32),
+}
+
+// virtio-gpu-3d transfer-related structs (begin)
+#[repr(C)]
+#[derive(Debug, Copy, Clone)]
+pub struct virgl_renderer_resource_create_args {
+    pub handle: u32,
+    pub target: u32,
+    pub format: u32,
+    pub bind: u32,
+    pub width: u32,
+    pub height: u32,
+    pub depth: u32,
+    pub array_size: u32,
+    pub last_level: u32,
+    pub nr_samples: u32,
+    pub flags: u32,
+}
+
+#[repr(C)]
+#[derive(Debug, Copy, Clone)]
+pub struct virgl_renderer_resource_info {
+    pub handle: u32,
+    pub virgl_format: u32,
+    pub width: u32,
+    pub height: u32,
+    pub depth: u32,
+    pub flags: u32,
+    pub tex_id: u32,
+    pub stride: u32,
+    pub drm_fourcc: c_int,
+}
+
+#[repr(C)]
+#[derive(Debug, Copy, Clone)]
+pub struct virgl_box {
+    pub x: u32,
+    pub y: u32,
+    pub z: u32,
+    pub w: u32,
+    pub h: u32,
+    pub d: u32,
+}
+
+#[repr(C)]
+#[derive(Debug, Copy, Clone)]
+pub struct iovec {
+    pub iov_base: *mut c_void,
+    pub iov_len: usize,
+}
+
+// virtio-gpu-3d transfer-related structs (end)
+
+#[link(name = "gfxstream_backend")]
+extern "C" {
+
+    // Function to globally init gfxstream backend's internal state, taking display/renderer
+    // parameters.
+    fn gfxstream_backend_init(
+        display_width: u32,
+        display_height: u32,
+        display_type: u32,
+        renderer_cookie: *mut c_void,
+        renderer_flags: i32,
+        renderer_callbacks: *mut GfxStreamRendererCallbacks,
+    );
+
+    // virtio-gpu-3d ioctl functions (begin)
+
+    // In gfxstream, the resource create/transfer ioctls correspond to creating buffers for API
+    // forwarding and the notification of new API calls forwarded by the guest, unless they
+    // correspond to minigbm resource targets (PIPE_TEXTURE_2D), in which case they create globally
+    // visible shared GL textures to support gralloc.
+    fn pipe_virgl_renderer_poll();
+    fn pipe_virgl_renderer_resource_create(
+        args: *mut virgl_renderer_resource_create_args,
+        iov: *mut iovec,
+        num_iovs: u32,
+    ) -> c_int;
+
+    fn pipe_virgl_renderer_resource_unref(res_handle: u32);
+    fn pipe_virgl_renderer_context_create(handle: u32, nlen: u32, name: *const c_char) -> c_int;
+    fn pipe_virgl_renderer_context_destroy(handle: u32);
+    fn pipe_virgl_renderer_transfer_read_iov(
+        handle: u32,
+        ctx_id: u32,
+        level: u32,
+        stride: u32,
+        layer_stride: u32,
+        box_: *mut virgl_box,
+        offset: u64,
+        iov: *mut iovec,
+        iovec_cnt: c_int,
+    ) -> c_int;
+    fn pipe_virgl_renderer_transfer_write_iov(
+        handle: u32,
+        ctx_id: u32,
+        level: c_int,
+        stride: u32,
+        layer_stride: u32,
+        box_: *mut virgl_box,
+        offset: u64,
+        iovec: *mut iovec,
+        iovec_cnt: c_uint,
+    ) -> c_int;
+    fn pipe_virgl_renderer_resource_attach_iov(
+        res_handle: c_int,
+        iov: *mut iovec,
+        num_iovs: c_int,
+    ) -> c_int;
+    fn pipe_virgl_renderer_resource_detach_iov(
+        res_handle: c_int,
+        iov: *mut *mut iovec,
+        num_iovs: *mut c_int,
+    );
+    fn pipe_virgl_renderer_create_fence(client_fence_id: c_int, ctx_id: u32) -> c_int;
+    fn pipe_virgl_renderer_ctx_attach_resource(ctx_id: c_int, res_handle: c_int);
+    fn pipe_virgl_renderer_ctx_detach_resource(ctx_id: c_int, res_handle: c_int);
+
+    fn stream_renderer_flush_resource_and_readback(
+        res_handle: u32,
+        x: u32,
+        y: u32,
+        width: u32,
+        height: u32,
+        pixels: *mut c_uchar,
+        max_bytes: u32,
+    );
+}
+
+// Fence state stuff (begin)
+
+struct FenceState {
+    latest_fence: u32,
+}
+impl FenceState {
+    pub fn write(&mut self, latest_fence: u32) {
+        if latest_fence > self.latest_fence {
+            self.latest_fence = latest_fence;
+        }
+    }
+}
+
+struct VirglCookie {
+    fence_state: Rc<RefCell<FenceState>>,
+}
+
+extern "C" fn write_fence(cookie: *mut c_void, fence: u32) {
+    assert!(!cookie.is_null());
+    let cookie = unsafe { &*(cookie as *mut VirglCookie) };
+
+    // Track the most recent fence.
+    let mut fence_state = cookie.fence_state.borrow_mut();
+    fence_state.write(fence);
+}
+
+const GFXSTREAM_RENDERER_CALLBACKS: &GfxStreamRendererCallbacks = &GfxStreamRendererCallbacks {
+    version: 1,
+    write_fence,
+};
+
+// Fence state stuff (end)
+
+pub struct VirtioGfxStreamBackend {
+    base: VirtioBackend,
+
+    /// Mapping from resource ids to in-use GuestMemory.
+    resources: Map<u32, Option<GuestMemory>>,
+
+    /// All commands processed by this backend are synchronous
+    /// and are either completed immediately or handled in a different layer,
+    /// so we just need to keep track of the latest created fence
+    /// and return that in fence_poll().
+    fence_state: Rc<RefCell<FenceState>>,
+}
+
+impl VirtioGfxStreamBackend {
+    pub fn new(
+        display: GpuDisplay,
+        display_width: u32,
+        display_height: u32,
+        _gpu_device_socket: VmMemoryControlRequestSocket,
+        _pci_bar: Alloc,
+    ) -> VirtioGfxStreamBackend {
+        let fence_state = Rc::new(RefCell::new(FenceState { latest_fence: 0 }));
+        let cookie: *mut VirglCookie = Box::into_raw(Box::new(VirglCookie {
+            fence_state: Rc::clone(&fence_state),
+        }));
+
+        let renderer_flags: RendererFlags = RendererFlags::new().use_surfaceless(true);
+
+        let display_rc_refcell = Rc::new(RefCell::new(display));
+
+        let scanout_surface = match (display_rc_refcell.borrow_mut()).create_surface(
+            None,
+            display_width,
+            display_height,
+        ) {
+            Ok(surface) => surface,
+            Err(e) => {
+                error!("Failed to create display surface: {}", e);
+                0
+            }
+        };
+
+        unsafe {
+            gfxstream_backend_init(
+                display_width,
+                display_height,
+                1, /* default to shmem display */
+                cookie as *mut c_void,
+                renderer_flags.into(),
+                transmute(GFXSTREAM_RENDERER_CALLBACKS),
+            );
+        }
+
+        VirtioGfxStreamBackend {
+            base: VirtioBackend {
+                display: Rc::clone(&display_rc_refcell),
+                display_width,
+                display_height,
+                event_devices: Default::default(),
+                scanout_resource_id: None,
+                scanout_surface_id: Some(scanout_surface),
+                cursor_resource_id: None,
+                cursor_surface_id: None,
+            },
+            resources: Default::default(),
+            fence_state,
+        }
+    }
+}
+
+impl Backend for VirtioGfxStreamBackend {
+    /// Returns the number of capsets provided by the Backend.
+    fn capsets() -> u32 {
+        1
+    }
+
+    /// Returns the bitset of virtio features provided by the Backend.
+    fn features() -> u64 {
+        1 << VIRTIO_GPU_F_VIRGL | 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(VirtioGfxStreamBackend::new(
+            display,
+            display_width,
+            display_height,
+            gpu_device_socket,
+            pci_bar,
+        )))
+    }
+
+    /// 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.
+    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 {
+        unsafe {
+            pipe_virgl_renderer_create_fence(fence_id as i32, ctx_id);
+        }
+        GpuResponse::OkNoData
+    }
+
+    /// Returns the id of the latest fence to complete.
+    fn fence_poll(&mut self) -> u32 {
+        unsafe {
+            pipe_virgl_renderer_poll();
+        }
+        self.fence_state.borrow().latest_fence
+    }
+
+    fn create_resource_2d(
+        &mut self,
+        _id: u32,
+        _width: u32,
+        _height: u32,
+        _format: u32,
+    ) -> GpuResponse {
+        // Not considered for gfxstream
+        GpuResponse::ErrUnspec
+    }
+
+    /// 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(_) => (),
+            None => {
+                return GpuResponse::ErrInvalidResourceId;
+            }
+        }
+
+        unsafe {
+            pipe_virgl_renderer_resource_unref(id);
+        }
+
+        GpuResponse::OkNoData
+    }
+
+    /// 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 {
+        GpuResponse::OkNoData
+    }
+
+    /// 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 {
+        // For now, always update the whole display.
+        let mut display_ref = self.base.display.borrow_mut();
+
+        let scanout_surface_id = match self.base.scanout_surface_id {
+            Some(id) => id,
+            _ => {
+                error!("No scanout surface created for backend!");
+                return GpuResponse::ErrInvalidResourceId;
+            }
+        };
+
+        let fb = match display_ref.framebuffer_region(
+            scanout_surface_id,
+            0,
+            0,
+            self.base.display_width,
+            self.base.display_height,
+        ) {
+            Some(fb) => fb,
+            None => {
+                panic!(
+                    "failed to access framebuffer for surface {}",
+                    scanout_surface_id
+                );
+            }
+        };
+
+        let fb_volatile_slice = fb.as_volatile_slice();
+        let fb_begin = fb_volatile_slice.as_ptr() as *mut c_uchar;
+        let fb_bytes = fb_volatile_slice.size() as usize;
+
+        unsafe {
+            stream_renderer_flush_resource_and_readback(
+                id,
+                0,
+                0,
+                self.base.display_width,
+                self.base.display_height,
+                fb_begin,
+                fb_bytes as u32,
+            );
+        }
+
+        display_ref.flip(scanout_surface_id);
+
+        GpuResponse::OkNoData
+    }
+
+    /// 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 {
+        // Not considered for gfxstream
+        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(entry) => {
+                *entry = Some(mem.clone());
+            }
+            None => {
+                return GpuResponse::ErrInvalidResourceId;
+            }
+        }
+
+        let mut backing_iovecs: Vec<iovec> = Vec::new();
+
+        for (addr, len) in vecs {
+            let slice = mem.get_slice(addr.offset(), len as u64).unwrap();
+            backing_iovecs.push(iovec {
+                iov_base: slice.as_ptr() as *mut c_void,
+                iov_len: len as usize,
+            });
+        }
+
+        unsafe {
+            pipe_virgl_renderer_resource_attach_iov(
+                id as i32,
+                backing_iovecs.as_mut_ptr() as *mut iovec,
+                backing_iovecs.len() as i32,
+            );
+        }
+        GpuResponse::OkNoData
+    }
+
+    /// 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(entry) => {
+                *entry = None;
+            }
+            None => {
+                return GpuResponse::ErrInvalidResourceId;
+            }
+        }
+
+        unsafe {
+            pipe_virgl_renderer_resource_detach_iov(
+                id as i32,
+                std::ptr::null_mut(),
+                std::ptr::null_mut(),
+            );
+        }
+        GpuResponse::OkNoData
+    }
+
+    fn update_cursor(&mut self, _id: u32, _x: u32, _y: u32) -> GpuResponse {
+        // Not considered for gfxstream
+        GpuResponse::OkNoData
+    }
+
+    fn move_cursor(&mut self, _x: u32, _y: u32) -> GpuResponse {
+        // Not considered for gfxstream
+        GpuResponse::OkNoData
+    }
+
+    fn get_capset_info(&self, index: u32) -> GpuResponse {
+        if 0 != index {
+            return GpuResponse::ErrUnspec;
+        }
+        GpuResponse::OkCapsetInfo {
+            id: index,
+            version: 1,
+            size: 0,
+        }
+    }
+
+    fn get_capset(&self, id: u32, _version: u32) -> GpuResponse {
+        if 0 != id {
+            return GpuResponse::ErrUnspec;
+        }
+        GpuResponse::OkCapset(Vec::new())
+    }
+
+    fn create_renderer_context(&mut self, id: u32) -> GpuResponse {
+        unsafe {
+            pipe_virgl_renderer_context_create(id, 1, std::ptr::null_mut());
+        }
+        GpuResponse::OkNoData
+    }
+
+    fn destroy_renderer_context(&mut self, id: u32) -> GpuResponse {
+        unsafe {
+            pipe_virgl_renderer_context_destroy(id);
+        }
+        GpuResponse::OkNoData
+    }
+
+    fn context_attach_resource(&mut self, ctx_id: u32, res_id: u32) -> GpuResponse {
+        unsafe {
+            pipe_virgl_renderer_ctx_attach_resource(ctx_id as i32, res_id as i32);
+        }
+        GpuResponse::OkNoData
+    }
+
+    fn context_detach_resource(&mut self, ctx_id: u32, res_id: u32) -> GpuResponse {
+        unsafe {
+            pipe_virgl_renderer_ctx_detach_resource(ctx_id as i32, res_id as i32);
+        }
+        GpuResponse::OkNoData
+    }
+
+    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::Vacant(slot) => {
+                slot.insert(None /* no guest memory attached yet */);
+            }
+            Entry::Occupied(_) => {
+                return GpuResponse::ErrInvalidResourceId;
+            }
+        }
+
+        let mut create_args = virgl_renderer_resource_create_args {
+            handle: id,
+            target,
+            format,
+            bind,
+            width,
+            height,
+            depth,
+            array_size,
+            last_level,
+            nr_samples,
+            flags,
+        };
+
+        unsafe {
+            pipe_virgl_renderer_resource_create(
+                &mut create_args as *mut virgl_renderer_resource_create_args,
+                std::ptr::null_mut(),
+                0,
+            );
+        }
+
+        GpuResponse::OkNoData
+    }
+
+    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 mut transfer_box = virgl_box {
+            x,
+            y,
+            z,
+            w: width,
+            h: height,
+            d: depth,
+        };
+
+        unsafe {
+            pipe_virgl_renderer_transfer_write_iov(
+                res_id,
+                ctx_id,
+                level as i32,
+                stride,
+                layer_stride,
+                &mut transfer_box as *mut virgl_box,
+                offset,
+                std::ptr::null_mut(),
+                0,
+            );
+        }
+        GpuResponse::OkNoData
+    }
+
+    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 mut transfer_box = virgl_box {
+            x,
+            y,
+            z,
+            w: width,
+            h: height,
+            d: depth,
+        };
+
+        unsafe {
+            pipe_virgl_renderer_transfer_read_iov(
+                res_id,
+                ctx_id,
+                level,
+                stride,
+                layer_stride,
+                &mut transfer_box as *mut virgl_box,
+                offset,
+                std::ptr::null_mut(),
+                0,
+            );
+        }
+        GpuResponse::OkNoData
+    }
+
+    // Not considered for gfxstream
+    fn submit_command(&mut self, _ctx_id: u32, _commands: &mut [u8]) -> GpuResponse {
+        GpuResponse::ErrUnspec
+    }
+
+    // Not considered for gfxstream
+    fn force_ctx_0(&mut self) {}
+}
diff --git a/src/main.rs b/src/main.rs
index 1e86e47..569935c 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -124,12 +124,37 @@ fn parse_gpu_options(s: Option<&str>) -> argument::Result<GpuParameters> {
 
         for (k, v) in opts {
             match k {
+                // Deprecated: Specifying --gpu=<mode> Not great as the mode can be set multiple
+                // times if the user specifies several modes (--gpu=2d,3d,gfxstream)
                 "2d" | "2D" => {
                     gpu_params.mode = GpuMode::Mode2D;
                 }
                 "3d" | "3D" => {
                     gpu_params.mode = GpuMode::Mode3D;
                 }
+                #[cfg(feature = "gfxstream")]
+                "gfxstream" => {
+                    gpu_params.mode = GpuMode::ModeGfxStream;
+                }
+                // Preferred: Specifying --gpu,backend=<mode>
+                "backend" => match v {
+                    "2d" | "2D" => {
+                        gpu_params.mode = GpuMode::Mode2D;
+                    }
+                    "3d" | "3D" => {
+                        gpu_params.mode = GpuMode::Mode3D;
+                    }
+                    #[cfg(feature = "gfxstream")]
+                    "gfxstream" => {
+                        gpu_params.mode = GpuMode::ModeGfxStream;
+                    }
+                    _ => {
+                        return Err(argument::Error::InvalidValue {
+                            value: v.to_string(),
+                            expected: "gpu parameter 'backend' should be one of (2d|3d|gfxstream)",
+                        });
+                    }
+                },
                 "egl" => match v {
                     "true" | "" => {
                         gpu_params.renderer_use_egl = true;
@@ -1209,6 +1234,7 @@ writeback=BOOL - Indicates whether the VM can use writeback caching (default: fa
                                   "[width=INT,height=INT]",
                                   "(EXPERIMENTAL) Comma separated key=value pairs for setting up a virtio-gpu device
                                   Possible key values:
+                                  backend=(2d|3d|gfxstream) - Which backend to use for virtio-gpu (determining rendering protocol)
                                   width=INT - The width of the virtual display connected to the virtio-gpu.
                                   height=INT - The height of the virtual display connected to the virtio-gpu.
                                   egl[=true|=false] - If the virtio-gpu backend should use a EGL context for rendering.