| // 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 std::usize; |
| |
| use data_model::*; |
| |
| use msg_socket::{MsgReceiver, MsgSender}; |
| use sys_util::{error, warn, GuestAddress, GuestMemory}; |
| |
| use gpu_buffer::{Buffer, Device, Flags, Format}; |
| use gpu_display::*; |
| use gpu_renderer::{ |
| format_fourcc as renderer_fourcc, Box3, Context as RendererContext, Image as RendererImage, |
| Renderer, Resource as GpuRendererResource, ResourceCreateArgs, VIRGL_RES_BIND_SCANOUT, |
| }; |
| |
| use super::protocol::{ |
| GpuResponse, GpuResponsePlaneInfo, VIRTIO_GPU_CAPSET_VIRGL, VIRTIO_GPU_CAPSET_VIRGL2, |
| }; |
| use crate::virtio::resource_bridge::*; |
| use vm_control::VmMemoryControlRequestSocket; |
| |
| 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, |
| src_offset: u64, |
| 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, |
| src_offset: u64, |
| _mem: &GuestMemory, |
| ) { |
| let res = self.transfer_write( |
| None, |
| 0, |
| 0, |
| 0, |
| Box3 { |
| x, |
| y, |
| z: 0, |
| w: width, |
| h: height, |
| d: 0, |
| }, |
| src_offset, |
| ); |
| if let Err(e) = res { |
| error!( |
| "failed to write to resource (x={} y={} w={} h={}, src_offset={}): {}", |
| x, y, width, height, src_offset, 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, |
| gpu_renderer_resource: Option<GpuRendererResource>, |
| _image: Option<RendererImage>, |
| } |
| |
| impl BackedBuffer { |
| fn new_renderer_registered( |
| buffer: Buffer, |
| gpu_renderer_resource: GpuRendererResource, |
| image: RendererImage, |
| ) -> BackedBuffer { |
| BackedBuffer { |
| display_import: None, |
| backing: Vec::new(), |
| buffer, |
| gpu_renderer_resource: Some(gpu_renderer_resource), |
| _image: Some(image), |
| } |
| } |
| } |
| |
| impl From<Buffer> for BackedBuffer { |
| fn from(buffer: Buffer) -> BackedBuffer { |
| BackedBuffer { |
| display_import: None, |
| backing: Vec::new(), |
| buffer, |
| gpu_renderer_resource: None, |
| _image: None, |
| } |
| } |
| } |
| |
| 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.clone(); |
| if let Some(resource) = &mut self.gpu_renderer_resource { |
| if let Err(e) = resource.attach_backing(&vecs[..], mem) { |
| error!("failed to attach backing to BackBuffer resource: {}", e); |
| } |
| } |
| } |
| |
| fn detach_guest_backing(&mut self) { |
| if let Some(resource) = &mut self.gpu_renderer_resource { |
| resource.detach_backing(); |
| } |
| self.backing.clear(); |
| } |
| |
| fn gpu_renderer_resource(&mut self) -> Option<&mut GpuRendererResource> { |
| self.gpu_renderer_resource.as_mut() |
| } |
| |
| fn buffer(&self) -> Option<&Buffer> { |
| Some(&self.buffer) |
| } |
| |
| 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); |
| } |
| } |
| 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, |
| src_offset: u64, |
| mem: &GuestMemory, |
| ) { |
| if src_offset >= usize::MAX as u64 { |
| error!( |
| "failed to write to resource with given offset: {}", |
| src_offset |
| ); |
| return; |
| } |
| let res = self.buffer.write_from_sg( |
| x, |
| y, |
| width, |
| height, |
| 0, // plane |
| src_offset as usize, |
| self.backing |
| .iter() |
| .map(|&(addr, len)| mem.get_slice(addr.offset(), len as u64).unwrap_or_default()), |
| ); |
| 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<dyn VirglResource>>, |
| contexts: Map<u32, RendererContext>, |
| #[allow(dead_code)] |
| gpu_device_socket: VmMemoryControlRequestSocket, |
| 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, |
| gpu_device_socket: VmMemoryControlRequestSocket, |
| ) -> Backend { |
| Backend { |
| display: Rc::new(RefCell::new(display)), |
| device, |
| renderer, |
| gpu_device_socket, |
| 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) |
| } |
| |
| 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; |
| } |
| }; |
| |
| let response = match request { |
| ResourceRequest::GetResource { id } => self |
| .resources |
| .get(&id) |
| .and_then(|resource| resource.buffer()) |
| .and_then(|buffer| buffer.export_plane_fd(0).ok()) |
| .map(ResourceResponse::Resource) |
| .unwrap_or(ResourceResponse::Invalid), |
| }; |
| |
| if let Err(e) = resource_bridge.send(&response) { |
| error!("error sending resource bridge request: {}", e); |
| } |
| } |
| |
| /// 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); |
| } |
| } |
| |
| 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, |
| src_offset: u64, |
| mem: &GuestMemory, |
| ) -> GpuResponse { |
| match self.resources.get_mut(&id) { |
| Some(res) => { |
| res.write_from_guest_memory(x, y, width, height, src_offset, 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 { |
| let id = match index { |
| 0 => VIRTIO_GPU_CAPSET_VIRGL, |
| 1 => VIRTIO_GPU_CAPSET_VIRGL2, |
| _ => return GpuResponse::ErrInvalidParameter, |
| }; |
| let (version, size) = self.renderer.get_cap_set_info(id); |
| GpuResponse::OkCapsetInfo { id, version, size } |
| } |
| |
| /// 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, |
| } |
| } |
| |
| pub fn allocate_using_minigbm(args: ResourceCreateArgs) -> bool { |
| args.bind & VIRGL_RES_BIND_SCANOUT != 0 |
| && args.depth == 1 |
| && args.array_size == 1 |
| && args.last_level == 0 |
| && args.nr_samples == 0 |
| } |
| |
| /// 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; |
| } |
| |
| let create_args = ResourceCreateArgs { |
| handle: id, |
| target, |
| format, |
| bind, |
| width, |
| height, |
| depth, |
| array_size, |
| last_level, |
| nr_samples, |
| flags, |
| }; |
| |
| match self.resources.entry(id) { |
| Entry::Occupied(_) => GpuResponse::ErrInvalidResourceId, |
| Entry::Vacant(slot) => { |
| if Backend::allocate_using_minigbm(create_args) { |
| match renderer_fourcc(create_args.format) { |
| Some(fourcc) => { |
| let buffer = match self.device.create_buffer( |
| width, |
| height, |
| Format::from(fourcc), |
| Flags::empty().use_scanout(true).use_rendering(true), |
| ) { |
| Ok(buffer) => buffer, |
| Err(_) => { |
| // Attempt to allocate the buffer without scanout flag. |
| match self.device.create_buffer( |
| width, |
| height, |
| Format::from(fourcc), |
| Flags::empty().use_rendering(true), |
| ) { |
| Ok(buffer) => buffer, |
| Err(e) => { |
| error!( |
| "failed to create buffer for 3d resource {}: {}", |
| format, e |
| ); |
| return GpuResponse::ErrUnspec; |
| } |
| } |
| } |
| }; |
| |
| let dma_buf_fd = match buffer.export_plane_fd(0) { |
| Ok(dma_buf_fd) => dma_buf_fd, |
| Err(e) => { |
| error!("failed to export plane fd: {}", e); |
| return GpuResponse::ErrUnspec; |
| } |
| }; |
| |
| let image = match self.renderer.image_from_dmabuf( |
| fourcc, |
| width, |
| height, |
| dma_buf_fd.as_raw_fd(), |
| buffer.plane_offset(0), |
| buffer.plane_stride(0), |
| ) { |
| Ok(image) => image, |
| Err(e) => { |
| error!("failed to create egl image: {}", e); |
| return GpuResponse::ErrUnspec; |
| } |
| }; |
| |
| let res = self.renderer.import_resource(create_args, &image); |
| match res { |
| Ok(res) => { |
| let format_modifier = buffer.format_modifier(); |
| let mut plane_info = Vec::with_capacity(buffer.num_planes()); |
| for plane_index in 0..buffer.num_planes() { |
| plane_info.push(GpuResponsePlaneInfo { |
| stride: buffer.plane_stride(plane_index), |
| offset: buffer.plane_offset(plane_index), |
| }); |
| } |
| let backed = |
| BackedBuffer::new_renderer_registered(buffer, res, image); |
| slot.insert(Box::new(backed)); |
| GpuResponse::OkResourcePlaneInfo { |
| format_modifier, |
| plane_info, |
| } |
| } |
| Err(e) => { |
| error!("failed to import renderer resource: {}", e); |
| GpuResponse::ErrUnspec |
| } |
| } |
| } |
| None => { |
| warn!( |
| "failed to get fourcc for minigbm 3d resource {}, falling back", |
| format |
| ); |
| let res = self.renderer.create_resource(create_args); |
| match res { |
| Ok(res) => { |
| slot.insert(Box::new(res)); |
| GpuResponse::OkNoData |
| } |
| Err(e) => { |
| error!("failed to create renderer resource: {}", e); |
| GpuResponse::ErrUnspec |
| } |
| } |
| } |
| } |
| } else { |
| let res = self.renderer.create_resource(create_args); |
| 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, |
| } |
| } |
| |
| 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(); |
| } |
| } |