#![cfg_attr(unix, allow(dead_code))]
use std::cell::RefCell;
use std::collections::HashMap;
use std::sync::mpsc::channel;
use std::sync::mpsc::Receiver;
use std::sync::mpsc::RecvTimeoutError;
use std::sync::mpsc::Sender;
use std::sync::Arc;
use std::time::Duration;
use anyhow::anyhow;
use anyhow::ensure;
use anyhow::format_err;
use anyhow::Context;
use anyhow::Result;
use ash::vk::UUID_SIZE;
use ash::vk::{self};
use base::error;
use base::warn;
use base::AsRawDescriptor;
use euclid::Box2D;
use euclid::Size2D;
use euclid::UnknownUnit;
use smallvec::SmallVec;
mod external_image;
mod post_worker;
mod sys;
pub use external_image::AcquireImageMemoryBarrier;
pub use external_image::ExternalImage;
pub use external_image::ExternalImageAccess;
pub use external_image::ReleaseImageMemoryBarrier;
use post_worker::Timepoint;
use post_worker::VulkanPostWorker;
use sync::create_promise_and_waitable;
use sync::Promise;
use sync::Waitable;
use vulkano::device::Device;
use vulkano::image::sys::UnsafeImageCreateInfo;
use vulkano::image::ImageCreateFlags;
use vulkano::image::ImageDimensions;
use vulkano::image::ImageLayout;
use vulkano::image::ImageUsage;
use vulkano::memory::ExternalMemoryHandleTypes;
use vulkano::memory::MemoryAllocateInfo;
use vulkano::sync::ExternalSemaphoreHandleTypes;
use vulkano::sync::Semaphore;
use vulkano::sync::SemaphoreCreateInfo;
use vulkano::sync::Sharing;
use vulkano::VulkanLibrary;
use vulkano::VulkanObject;
use self::sys::platform::create_post_image_external_memory_handle_types;
use self::sys::platform::create_post_image_memory_import_info;
use self::sys::platform::import_semaphore_from_descriptor;
use self::sys::platform::NativeWindowType;
use self::sys::ApplicationState;
use self::sys::ApplicationStateBuilder;
pub(crate) use self::sys::PlatformWindowEventLoop;
use self::sys::Window;
use self::sys::WindowEvent;
use self::sys::WindowEventLoop;
use crate::SemaphoreTimepoint;
use crate::VulkanDisplayImageImportMetadata;
pub type SemaphoreId = u32;
pub type ImageId = u32;
pub enum UserEvent {
GetVulkanDevice(Sender<Arc<Device>>),
PostCommand {
image: ExternalImage,
last_layout_transition: (ImageLayout, ImageLayout),
acquire_timepoint: Option<Timepoint>,
release_timepoint: Timepoint,
image_return: Sender<ExternalImage>,
promise: Promise,
},
}
pub struct HostState {
post_worker: RefCell<VulkanPostWorker>,
}
impl ApplicationState for HostState {
type UserEvent = UserEvent;
fn process_event(&self, event: WindowEvent<Self::UserEvent>) {
match event {
WindowEvent::User(UserEvent::GetVulkanDevice(sender)) => {
sender
.send(self.post_worker.borrow().device())
.expect("Should send VkDevice back to the caller successfully.");
}
WindowEvent::User(UserEvent::PostCommand {
image,
last_layout_transition,
acquire_timepoint,
release_timepoint,
image_return,
promise,
}) => {
let image = self.post_worker.borrow_mut().post(
image,
last_layout_transition,
acquire_timepoint,
release_timepoint,
);
promise.signal();
image_return
.send(image)
.expect("Should send ExternalImage back to the caller successfully.");
}
WindowEvent::Resized => {
if let Err(err) = self.post_worker.borrow_mut().recreate_swapchain() {
panic!(
concat!(
"Failed to recreate the swapchain when handling the Resized window ",
"event: {:?}."
),
err
);
}
}
}
}
}
struct HostStateBuilder {
vulkan_library: Arc<VulkanLibrary>,
device_uuid: [u8; vk::UUID_SIZE],
driver_uuid: [u8; vk::UUID_SIZE],
}
impl ApplicationStateBuilder for HostStateBuilder {
type Target = HostState;
fn build<T: Window>(self, window: Arc<T>) -> Result<HostState> {
let post_worker = VulkanPostWorker::new(
self.vulkan_library,
&self.device_uuid,
&self.driver_uuid,
Arc::clone(&window) as _,
)
.context("creating the post worker")?;
Ok(HostState {
post_worker: RefCell::new(post_worker),
})
}
}
pub struct HostDisplayImpl<T: WindowEventLoop<HostState>> {
ash_device: ash::Device,
device: Arc<Device>,
window_event_loop: T,
imported_semaphores: HashMap<SemaphoreId, Arc<Semaphore>>,
imported_images: HashMap<ImageId, ExternalImage>,
used_image_receivers: HashMap<ImageId, Receiver<ExternalImage>>,
}
impl<T: WindowEventLoop<HostState>> HostDisplayImpl<T> {
#[deny(unsafe_op_in_unsafe_fn)]
pub unsafe fn new(
vulkan_library: Arc<VulkanLibrary>,
parent: NativeWindowType,
initial_window_size: &Size2D<i32, UnknownUnit>,
device_uuid: [u8; UUID_SIZE],
driver_uuid: [u8; UUID_SIZE],
) -> Result<Self> {
let vulkan_state_builder = HostStateBuilder {
vulkan_library,
device_uuid,
driver_uuid,
};
let window_event_loop = unsafe {
T::create(parent, initial_window_size, vulkan_state_builder)
.context("create window and event loop")?
};
let (vk_device_tx, vk_device_rx) = channel();
window_event_loop
.send_event(UserEvent::GetVulkanDevice(vk_device_tx))
.context("retrieve VkDevice from the window event loop")?;
let device = loop {
const TIMEOUT: Duration = Duration::from_secs(60);
match vk_device_rx.recv_timeout(TIMEOUT) {
Ok(value) => break value,
Err(RecvTimeoutError::Timeout) => {
warn!(
"Didn't receive the VkDevice from the event loop for {:?}. Retry.",
TIMEOUT
);
continue;
}
Err(e) => {
return Err(format_err!(
"Failed to receive VkDevice from the event loop: {:?}.",
e
));
}
}
};
let ash_device =
unsafe { ash::Device::load(&device.instance().fns().v1_0, device.internal_object()) };
Ok(Self {
ash_device,
device,
window_event_loop,
imported_semaphores: Default::default(),
imported_images: Default::default(),
used_image_receivers: Default::default(),
})
}
pub fn move_window(&self, pos: &Box2D<i32, UnknownUnit>) -> Result<()> {
self.window_event_loop.move_window(pos)
}
pub fn import_semaphore(
&mut self,
semaphore_id: SemaphoreId,
descriptor: &dyn AsRawDescriptor,
) -> Result<()> {
let mut type_create_info = vk::SemaphoreTypeCreateInfo::builder()
.semaphore_type(vk::SemaphoreType::TIMELINE)
.initial_value(0)
.build();
let create_info = vk::SemaphoreCreateInfo::builder()
.push_next(&mut type_create_info)
.build();
let semaphore = unsafe { self.ash_device.create_semaphore(&create_info, None) }
.context("create timeline semaphore")?;
let res = import_semaphore_from_descriptor(&self.device, semaphore, descriptor);
ensure!(
res == vk::Result::SUCCESS,
"Failed to import the external handle to the semaphore: {}.",
res
);
let res = unsafe {
Semaphore::from_handle(
Arc::clone(&self.device),
semaphore,
SemaphoreCreateInfo {
export_handle_types: ExternalSemaphoreHandleTypes::empty(),
..Default::default()
},
)
};
if self
.imported_semaphores
.insert(semaphore_id, Arc::new(res))
.is_some()
{
warn!("Reused semaphore_id {}", semaphore_id);
}
Ok(())
}
pub fn import_image(
&mut self,
image_id: ImageId,
descriptor: &dyn AsRawDescriptor,
metadata: VulkanDisplayImageImportMetadata,
) -> Result<()> {
let image_create_flags = metadata.flags;
let ImageCreateFlags {
sparse_binding,
sparse_residency,
sparse_aliased,
mutable_format,
cube_compatible,
array_2d_compatible,
block_texel_view_compatible,
_ne: _,
} = vk::ImageCreateFlags::from_raw(image_create_flags).into();
assert!(
!(sparse_binding || sparse_residency || sparse_aliased),
"unsupported image create flags {image_create_flags:#x}"
);
let image_type = vk::ImageType::from_raw(metadata.image_type);
let image_extent = metadata.extent;
let image_dimensions = match image_type {
vk::ImageType::TYPE_2D => ImageDimensions::Dim2d {
width: image_extent.width,
height: image_extent.height,
array_layers: metadata.array_layers,
},
_ => unimplemented!(),
};
let format = {
let format = metadata.format;
vk::Format::from_raw(format)
.try_into()
.map_err(|_| format_err!("Failed to convert {:#x} to format.", format))?
};
let image_samples = {
let samples = metadata.samples;
vk::SampleCountFlags::from_raw(samples)
.try_into()
.map_err(|_| {
format_err!("Failed to convert {:#x} to sample count flag.", samples)
})?
};
let image_tiling = {
let tiling = metadata.tiling;
vk::ImageTiling::from_raw(tiling)
.try_into()
.map_err(|_| format_err!("Failed to convert {:#x} to image tiling enum.", tiling))?
};
let image_usage = {
let usage = metadata.usage;
vk::ImageUsageFlags::from_raw(usage).into()
};
let image_sharing = {
let sharing_mode = metadata.sharing_mode;
match vk::SharingMode::from_raw(sharing_mode) {
vk::SharingMode::EXCLUSIVE => Sharing::Exclusive,
vk::SharingMode::CONCURRENT => {
let mut queue_family_indices = SmallVec::new();
queue_family_indices.copy_from_slice(&metadata.queue_family_indices);
Sharing::Concurrent(queue_family_indices)
}
_ => return Err(format_err!("Invalid sharing mode {:#x}.", sharing_mode)),
}
};
let image_initial_layout = {
let initial_layout = metadata.initial_layout;
vk::ImageLayout::from_raw(initial_layout)
.try_into()
.map_err(|_| {
format_err!(
"Failed to convert the initial layout {:#x} to an image layout.",
initial_layout
)
})?
};
let image = ExternalImage::import(
&self.device,
UnsafeImageCreateInfo {
dimensions: image_dimensions,
format: Some(format),
mip_levels: metadata.mip_levels,
samples: image_samples,
tiling: image_tiling,
usage: image_usage,
stencil_usage: ImageUsage::empty(),
sharing: image_sharing,
initial_layout: image_initial_layout,
external_memory_handle_types: create_post_image_external_memory_handle_types(),
mutable_format,
cube_compatible,
array_2d_compatible,
block_texel_view_compatible,
..Default::default()
},
MemoryAllocateInfo {
allocation_size: metadata.allocation_size,
memory_type_index: metadata.memory_type_index,
export_handle_types: ExternalMemoryHandleTypes::empty(),
..Default::default()
},
create_post_image_memory_import_info(descriptor),
metadata.dedicated_allocation,
0,
)
.context("import the composition result image")?;
if self.imported_images.insert(image_id, image).is_some() {
warn!("Reused image_id {}", image_id);
}
Ok(())
}
pub fn delete_imported_image_or_semaphore(&mut self, import_id: u32) {
if self.imported_semaphores.remove(&import_id).is_none() {
if let Some(receiver) = self.used_image_receivers.remove(&import_id) {
if let Err(e) = receiver.recv() {
error!("Failed to receive used image from post worker: {}", e);
}
} else if self.imported_images.remove(&import_id).is_none() {
error!("Import id {} has not been imported", import_id);
}
}
}
pub fn post(
&mut self,
image_id: ImageId,
last_layout_transition: (i32, i32),
acquire_semaphore: Option<SemaphoreTimepoint>,
release_semaphore: SemaphoreTimepoint,
) -> Result<Waitable> {
let image = if let Some(receiver) = self.used_image_receivers.remove(&image_id) {
receiver
.recv()
.context("failed to receive used image from post worker")?
} else {
self.imported_images
.remove(&image_id)
.ok_or(anyhow!("Image id {} has not been imported", image_id))?
};
let acquire_timepoint =
if let Some(SemaphoreTimepoint { import_id, value }) = acquire_semaphore {
let semaphore = self
.imported_semaphores
.get(&import_id)
.ok_or(anyhow!("Semaphore id {} has not been imported", import_id))?;
Some(Timepoint {
semaphore: semaphore.clone(),
value,
})
} else {
None
};
let release_timepoint = {
let semaphore = self
.imported_semaphores
.get(&release_semaphore.import_id)
.ok_or(anyhow!(
"Semaphore id {} has not been imported",
release_semaphore.import_id
))?;
Timepoint {
semaphore: semaphore.clone(),
value: release_semaphore.value,
}
};
let last_layout_transition: (ImageLayout, ImageLayout) = (
ash::vk::ImageLayout::from_raw(last_layout_transition.0)
.try_into()
.map_err(|_| {
anyhow!(
"Failed to convert {:#x} to a valid image layout.",
last_layout_transition.0
)
})?,
ash::vk::ImageLayout::from_raw(last_layout_transition.1)
.try_into()
.map_err(|_| {
anyhow!(
"Failed to convert {:#x} to a valid image layout.",
last_layout_transition.1
)
})?,
);
let (promise, waitable) = create_promise_and_waitable();
let (image_return_tx, image_return_rx) = channel();
self.used_image_receivers.insert(image_id, image_return_rx);
self.window_event_loop
.send_event(UserEvent::PostCommand {
image,
last_layout_transition,
acquire_timepoint,
release_timepoint,
image_return: image_return_tx,
promise,
})
.context("send user defined message to the window event loop")?;
Ok(waitable)
}
}
pub(crate) type HostDisplay = HostDisplayImpl<PlatformWindowEventLoop<HostState>>;