summary refs log blame commit diff
path: root/devices/src/virtio/gpu/virtio_backend.rs
blob: bb1db4a7811f4d266a26fb8015a1b75caa9e3a89 (plain) (tree)




























































                                                                                               
                                                                                













































                                                                                                  


                                                                                      













































































































































































                                                                                                    
// 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 {
            error!("got nonzero scanout: {:}, but only support zero.", scanout);
            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);
                        for (event_device_id, _) in &self.event_devices {
                            display.attach_event_device(surface_id, *event_device_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
    }
}