blob: 5ed01e1bbc49e142866fff96c039dee49b6d19b8 [file] [log] [blame]
// Copyright 2021 The ChromiumOS Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
//! vulkano_gralloc: Implements swapchain allocation and memory mapping
//! using Vulkano.
//!
//! External code found at https://github.com/vulkano-rs/vulkano.
#![cfg(feature = "vulkano")]
mod sys;
use std::collections::HashMap as Map;
use std::convert::TryInto;
use std::sync::Arc;
use log::warn;
use vulkano::device::physical::PhysicalDeviceType;
use vulkano::device::Device;
use vulkano::device::DeviceCreateInfo;
use vulkano::device::DeviceCreationError;
use vulkano::device::QueueCreateInfo;
use vulkano::image;
use vulkano::image::ImageCreationError;
use vulkano::image::ImageDimensions;
use vulkano::image::ImageUsage;
use vulkano::image::SampleCount;
use vulkano::instance::Instance;
use vulkano::instance::InstanceCreateInfo;
use vulkano::instance::InstanceCreationError;
use vulkano::instance::InstanceExtensions;
use vulkano::instance::Version;
use vulkano::memory::pool::AllocFromRequirementsFilter;
use vulkano::memory::DedicatedAllocation;
use vulkano::memory::DeviceMemory;
use vulkano::memory::DeviceMemoryError;
use vulkano::memory::ExternalMemoryHandleType;
use vulkano::memory::ExternalMemoryHandleTypes;
use vulkano::memory::MappedDeviceMemory;
use vulkano::memory::MemoryAllocateInfo;
use vulkano::memory::MemoryMapError;
use vulkano::memory::MemoryRequirements;
use vulkano::memory::MemoryType;
use vulkano::sync::Sharing;
use vulkano::LoadingError;
use vulkano::VulkanError;
use vulkano::VulkanLibrary;
use crate::rutabaga_gralloc::gralloc::Gralloc;
use crate::rutabaga_gralloc::gralloc::ImageAllocationInfo;
use crate::rutabaga_gralloc::gralloc::ImageMemoryRequirements;
use crate::rutabaga_os::MappedRegion;
use crate::rutabaga_utils::*;
/// A gralloc implementation capable of allocation `VkDeviceMemory`.
pub struct VulkanoGralloc {
devices: Map<PhysicalDeviceType, Arc<Device>>,
device_by_id: Map<DeviceId, Arc<Device>>,
has_integrated_gpu: bool,
}
struct VulkanoMapping {
mapped_memory: MappedDeviceMemory,
size: usize,
}
impl VulkanoMapping {
pub fn new(mapped_memory: MappedDeviceMemory, size: usize) -> VulkanoMapping {
VulkanoMapping {
mapped_memory,
size,
}
}
}
unsafe impl MappedRegion for VulkanoMapping {
/// Used for passing this region for hypervisor memory mappings. We trust crosvm to use this
/// safely.
fn as_ptr(&self) -> *mut u8 {
unsafe {
// Will not panic since the requested range of the device memory was verified on
// creation
let x = self.mapped_memory.write(0..self.size as u64).unwrap();
x.as_mut_ptr()
}
}
/// Returns the size of the memory region in bytes.
fn size(&self) -> usize {
self.size
}
}
trait DeviceExt {
/// Get a unique identifier for the device.
fn get_id(&self) -> DeviceId;
}
impl DeviceExt for Device {
fn get_id(&self) -> DeviceId {
let properties = self.physical_device().properties();
DeviceId {
device_uuid: properties.device_uuid.expect("Vulkan should support uuid"),
driver_uuid: properties.driver_uuid.expect("Vulkan should support uuid"),
}
}
}
impl VulkanoGralloc {
/// Returns a new `VulkanGralloc' instance upon success.
pub fn init() -> RutabagaResult<Box<dyn Gralloc>> {
// Initialization copied from triangle.rs in Vulkano. Look there for a more detailed
// explanation of VK initialization.
let library = VulkanLibrary::new()?;
let instance_extensions = InstanceExtensions {
khr_external_memory_capabilities: true,
khr_get_physical_device_properties2: true,
..InstanceExtensions::empty()
};
let instance = Instance::new(
library,
InstanceCreateInfo {
enabled_extensions: instance_extensions,
max_api_version: Some(Version::V1_1),
..Default::default()
},
)?;
let mut devices: Map<PhysicalDeviceType, Arc<Device>> = Default::default();
let mut device_by_id: Map<DeviceId, Arc<Device>> = Default::default();
let mut has_integrated_gpu = false;
for physical in instance.enumerate_physical_devices()? {
let queue_family_index = match physical.queue_family_properties().iter().position(|q| {
// We take the first queue family that supports graphics.
q.queue_flags.graphics
}) {
Some(family) => family,
None => {
warn!(
"Skipping Vulkano for {} due to no graphics queue",
physical.properties().device_name
);
continue;
}
};
let supported_extensions = physical.supported_extensions();
let desired_extensions = Self::get_desired_device_extensions();
let intersection = supported_extensions.intersection(&desired_extensions);
if let Ok((device, mut _queues)) = Device::new(
physical.clone(),
DeviceCreateInfo {
enabled_extensions: intersection,
queue_create_infos: vec![QueueCreateInfo {
queue_family_index: queue_family_index as u32,
..Default::default()
}],
..Default::default()
},
) {
let device_type = device.physical_device().properties().device_type;
if device_type == PhysicalDeviceType::IntegratedGpu {
has_integrated_gpu = true
}
// If we have two devices of the same type (two integrated GPUs), the old value is
// dropped. Vulkano is verbose enough such that a keener selection algorithm may
// be used, but the need for such complexity does not seem to exist now.
devices.insert(device_type, device.clone());
device_by_id.insert(device.get_id(), device);
};
}
if devices.is_empty() {
return Err(RutabagaError::SpecViolation(
"no matching VK devices available",
));
}
Ok(Box::new(VulkanoGralloc {
devices,
device_by_id,
has_integrated_gpu,
}))
}
// This function is used safely in this module because gralloc does not:
//
// (1) bind images to any memory.
// (2) transition the layout of images.
// (3) transfer ownership of images between queues.
//
// In addition, we trust Vulkano to validate image parameters are within the Vulkan spec.
// TODO(tutankhamen): Do we still need a separate MemoryRequirements?
unsafe fn create_image(
&mut self,
info: ImageAllocationInfo,
) -> RutabagaResult<(Arc<image::sys::UnsafeImage>, MemoryRequirements)> {
let device = if self.has_integrated_gpu {
self.devices
.get(&PhysicalDeviceType::IntegratedGpu)
.ok_or(RutabagaError::InvalidGrallocGpuType)?
} else {
self.devices
.get(&PhysicalDeviceType::DiscreteGpu)
.ok_or(RutabagaError::InvalidGrallocGpuType)?
};
let usage = match info.flags.uses_rendering() {
true => ImageUsage {
color_attachment: true,
..ImageUsage::empty()
},
false => ImageUsage {
sampled: true,
..ImageUsage::empty()
},
};
// Reasonable bounds on image width.
if info.width == 0 || info.width > 4096 {
return Err(RutabagaError::InvalidGrallocDimensions);
}
// Reasonable bounds on image height.
if info.height == 0 || info.height > 4096 {
return Err(RutabagaError::InvalidGrallocDimensions);
}
let vulkan_format = info.drm_format.vulkan_format()?;
let unsafe_image = image::sys::UnsafeImage::new(
device.clone(),
image::sys::UnsafeImageCreateInfo {
dimensions: ImageDimensions::Dim2d {
width: info.width,
height: info.height,
array_layers: 1,
},
format: Some(vulkan_format),
samples: SampleCount::Sample1,
usage,
mip_levels: 1,
sharing: Sharing::Exclusive,
..Default::default()
},
)?;
let memory_requirements = unsafe_image.memory_requirements();
Ok((unsafe_image, memory_requirements))
}
}
impl Gralloc for VulkanoGralloc {
fn supports_external_gpu_memory(&self) -> bool {
for device in self.devices.values() {
if !device.enabled_extensions().khr_external_memory {
return false;
}
}
true
}
fn supports_dmabuf(&self) -> bool {
for device in self.devices.values() {
if !device.enabled_extensions().ext_external_memory_dma_buf {
return false;
}
}
true
}
fn get_image_memory_requirements(
&mut self,
info: ImageAllocationInfo,
) -> RutabagaResult<ImageMemoryRequirements> {
let mut reqs: ImageMemoryRequirements = Default::default();
let (unsafe_image, memory_requirements) = unsafe { self.create_image(info)? };
let device_type = if self.has_integrated_gpu {
&PhysicalDeviceType::IntegratedGpu
} else {
&PhysicalDeviceType::DiscreteGpu
};
let device = self
.devices
.get(device_type)
.ok_or(RutabagaError::InvalidGrallocGpuType)?;
let planar_layout = info.drm_format.planar_layout()?;
// Safe because we created the image with the linear bit set and verified the format is
// not a depth or stencil format. We are also using the correct image aspect. Vulkano
// will panic if we are not.
for plane in 0..planar_layout.num_planes {
let aspect = info.drm_format.vulkan_image_aspect(plane)?;
let layout = unsafe { unsafe_image.multiplane_color_layout(aspect) };
reqs.strides[plane] = layout.row_pitch as u32;
reqs.offsets[plane] = layout.offset as u32;
}
let need_visible = info.flags.host_visible();
let want_cached = info.flags.host_cached();
let (memory_type_index, memory_type) = {
let filter = |current_type: &MemoryType| {
if need_visible && !current_type.property_flags.host_visible {
return AllocFromRequirementsFilter::Forbidden;
}
if !need_visible && current_type.property_flags.device_local {
return AllocFromRequirementsFilter::Preferred;
}
if need_visible && want_cached && current_type.property_flags.host_cached {
return AllocFromRequirementsFilter::Preferred;
}
if need_visible
&& !want_cached
&& current_type.property_flags.host_coherent
&& !current_type.property_flags.host_cached
{
return AllocFromRequirementsFilter::Preferred;
}
AllocFromRequirementsFilter::Allowed
};
let first_loop = device
.physical_device()
.memory_properties()
.memory_types
.iter()
.enumerate()
.map(|(i, t)| (i, t, AllocFromRequirementsFilter::Preferred));
let second_loop = device
.physical_device()
.memory_properties()
.memory_types
.iter()
.enumerate()
.map(|(i, t)| (i, t, AllocFromRequirementsFilter::Allowed));
let found_type = first_loop
.chain(second_loop)
.filter(|&(i, _, _)| (memory_requirements.memory_type_bits & (1 << i)) != 0)
.find(|&(_, t, rq)| filter(t) == rq)
.ok_or(RutabagaError::SpecViolation(
"unable to find required memory type",
))?;
(found_type.0, found_type.1)
};
reqs.info = info;
reqs.size = memory_requirements.size as u64;
if memory_type.property_flags.host_visible {
if memory_type.property_flags.host_cached {
reqs.map_info = RUTABAGA_MAP_CACHE_CACHED;
} else if memory_type.property_flags.host_coherent {
reqs.map_info = RUTABAGA_MAP_CACHE_WC;
}
}
reqs.vulkan_info = Some(VulkanInfo {
memory_idx: memory_type_index as u32,
device_id: device.get_id(),
});
Ok(reqs)
}
fn allocate_memory(&mut self, reqs: ImageMemoryRequirements) -> RutabagaResult<RutabagaHandle> {
let (unsafe_image, memory_requirements) = unsafe { self.create_image(reqs.info)? };
let vulkan_info = reqs.vulkan_info.ok_or(RutabagaError::InvalidVulkanInfo)?;
let device = if self.has_integrated_gpu {
self.devices
.get(&PhysicalDeviceType::IntegratedGpu)
.ok_or(RutabagaError::InvalidGrallocGpuType)?
} else {
self.devices
.get(&PhysicalDeviceType::DiscreteGpu)
.ok_or(RutabagaError::InvalidGrallocGpuType)?
};
if vulkan_info.memory_idx as usize
>= device
.physical_device()
.memory_properties()
.memory_types
.len()
{
return Err(RutabagaError::InvalidVulkanInfo);
}
let (export_handle_type, export_handle_types, rutabaga_type) =
match device.enabled_extensions().ext_external_memory_dma_buf {
true => (
ExternalMemoryHandleType::DmaBuf,
ExternalMemoryHandleTypes {
dma_buf: true,
..ExternalMemoryHandleTypes::empty()
},
RUTABAGA_MEM_HANDLE_TYPE_DMABUF,
),
false => (
ExternalMemoryHandleType::OpaqueFd,
ExternalMemoryHandleTypes {
opaque_fd: true,
..ExternalMemoryHandleTypes::empty()
},
RUTABAGA_MEM_HANDLE_TYPE_OPAQUE_FD,
),
};
let dedicated_allocation = match device.enabled_extensions().khr_dedicated_allocation {
true => {
if memory_requirements.prefer_dedicated {
Some(DedicatedAllocation::Image(&unsafe_image))
} else {
None
}
}
false => None,
};
let device_memory = DeviceMemory::allocate(
device.clone(),
MemoryAllocateInfo {
allocation_size: reqs.size,
memory_type_index: vulkan_info.memory_idx,
dedicated_allocation,
export_handle_types,
..Default::default()
},
)?;
let descriptor = device_memory.export_fd(export_handle_type)?.into();
Ok(RutabagaHandle {
os_handle: descriptor,
handle_type: rutabaga_type,
})
}
/// Implementations must map the memory associated with the `resource_id` upon success.
fn import_and_map(
&mut self,
handle: RutabagaHandle,
vulkan_info: VulkanInfo,
size: u64,
) -> RutabagaResult<Box<dyn MappedRegion>> {
let device = self
.device_by_id
.get(&vulkan_info.device_id)
.ok_or(RutabagaError::InvalidVulkanInfo)?;
let device_memory = unsafe {
VulkanoGralloc::import_memory(
device.clone(),
MemoryAllocateInfo {
allocation_size: size,
memory_type_index: vulkan_info.memory_idx,
..Default::default()
},
handle,
)?
};
let mapped_memory = MappedDeviceMemory::new(device_memory, 0..size)?;
Ok(Box::new(VulkanoMapping::new(
mapped_memory,
size.try_into()?,
)))
}
}
// Vulkano should really define an universal type that wraps all these errors, say
// "VulkanoError(e)".
impl From<InstanceCreationError> for RutabagaError {
fn from(e: InstanceCreationError) -> RutabagaError {
RutabagaError::VkInstanceCreationError(e)
}
}
impl From<ImageCreationError> for RutabagaError {
fn from(e: ImageCreationError) -> RutabagaError {
RutabagaError::VkImageCreationError(e)
}
}
impl From<DeviceCreationError> for RutabagaError {
fn from(e: DeviceCreationError) -> RutabagaError {
RutabagaError::VkDeviceCreationError(e)
}
}
impl From<DeviceMemoryError> for RutabagaError {
fn from(e: DeviceMemoryError) -> RutabagaError {
RutabagaError::VkDeviceMemoryError(e)
}
}
impl From<MemoryMapError> for RutabagaError {
fn from(e: MemoryMapError) -> RutabagaError {
RutabagaError::VkMemoryMapError(e)
}
}
impl From<LoadingError> for RutabagaError {
fn from(e: LoadingError) -> RutabagaError {
RutabagaError::VkLoadingError(e)
}
}
impl From<VulkanError> for RutabagaError {
fn from(e: VulkanError) -> RutabagaError {
RutabagaError::VkError(e)
}
}