use super::{
sys::{CommandBufferAllocateInfo, UnsafeCommandPoolCreateInfo, UnsafeCommandPoolCreationError},
CommandPool, CommandPoolAlloc, CommandPoolBuilderAlloc, UnsafeCommandPool,
UnsafeCommandPoolAlloc,
};
use crate::{
command_buffer::CommandBufferLevel,
device::{physical::QueueFamily, Device, DeviceOwned},
OomError, VulkanObject,
};
use crossbeam_queue::SegQueue;
use std::{
collections::HashMap,
marker::PhantomData,
mem::ManuallyDrop,
ptr,
sync::{Arc, Mutex, Weak},
thread,
vec::IntoIter as VecIntoIter,
};
#[derive(Debug)]
pub struct StandardCommandPool {
device: Arc<Device>,
queue_family: u32,
per_thread: Mutex<HashMap<thread::ThreadId, Weak<StandardCommandPoolPerThread>>>,
}
unsafe impl Send for StandardCommandPool {}
unsafe impl Sync for StandardCommandPool {}
#[derive(Debug)]
struct StandardCommandPoolPerThread {
pool: Mutex<UnsafeCommandPool>,
available_primary_command_buffers: SegQueue<UnsafeCommandPoolAlloc>,
available_secondary_command_buffers: SegQueue<UnsafeCommandPoolAlloc>,
}
impl StandardCommandPool {
pub fn new(device: Arc<Device>, queue_family: QueueFamily) -> StandardCommandPool {
assert_eq!(
device.physical_device().internal_object(),
queue_family.physical_device().internal_object()
);
StandardCommandPool {
device: device,
queue_family: queue_family.id(),
per_thread: Mutex::new(Default::default()),
}
}
}
unsafe impl CommandPool for Arc<StandardCommandPool> {
type Iter = VecIntoIter<StandardCommandPoolBuilder>;
type Builder = StandardCommandPoolBuilder;
type Alloc = StandardCommandPoolAlloc;
fn allocate(
&self,
level: CommandBufferLevel,
mut command_buffer_count: u32,
) -> Result<Self::Iter, OomError> {
let mut hashmap = self.per_thread.lock().unwrap();
hashmap.retain(|_, w| w.upgrade().is_some());
let this_thread = thread::current().id();
let per_thread = if let Some(entry) = hashmap.get(&this_thread).and_then(Weak::upgrade) {
entry
} else {
let new_pool = UnsafeCommandPool::new(
self.device.clone(),
UnsafeCommandPoolCreateInfo {
queue_family_index: self.queue_family().id(),
reset_command_buffer: true,
..Default::default()
},
)
.map_err(|err| match err {
UnsafeCommandPoolCreationError::OomError(err) => err,
_ => panic!("Unexpected error: {}", err),
})?;
let pt = Arc::new(StandardCommandPoolPerThread {
pool: Mutex::new(new_pool),
available_primary_command_buffers: SegQueue::new(),
available_secondary_command_buffers: SegQueue::new(),
});
hashmap.insert(this_thread, Arc::downgrade(&pt));
pt
};
let mut output = Vec::with_capacity(command_buffer_count as usize);
{
let existing = match level {
CommandBufferLevel::Primary => &per_thread.available_primary_command_buffers,
CommandBufferLevel::Secondary => &per_thread.available_secondary_command_buffers,
};
for _ in 0..command_buffer_count as usize {
if let Some(cmd) = existing.pop() {
output.push(StandardCommandPoolBuilder {
inner: StandardCommandPoolAlloc {
cmd: ManuallyDrop::new(cmd),
pool: per_thread.clone(),
pool_parent: self.clone(),
level,
device: self.device.clone(),
},
dummy_avoid_send_sync: PhantomData,
});
} else {
break;
}
}
};
if output.len() < command_buffer_count as usize {
let pool_lock = per_thread.pool.lock().unwrap();
command_buffer_count -= output.len() as u32;
for cmd in pool_lock.allocate_command_buffers(CommandBufferAllocateInfo {
level,
command_buffer_count,
..Default::default()
})? {
output.push(StandardCommandPoolBuilder {
inner: StandardCommandPoolAlloc {
cmd: ManuallyDrop::new(cmd),
pool: per_thread.clone(),
pool_parent: self.clone(),
level,
device: self.device.clone(),
},
dummy_avoid_send_sync: PhantomData,
});
}
}
Ok(output.into_iter())
}
#[inline]
fn queue_family(&self) -> QueueFamily {
self.device
.physical_device()
.queue_family_by_id(self.queue_family)
.unwrap()
}
}
unsafe impl DeviceOwned for StandardCommandPool {
#[inline]
fn device(&self) -> &Arc<Device> {
&self.device
}
}
pub struct StandardCommandPoolBuilder {
inner: StandardCommandPoolAlloc,
dummy_avoid_send_sync: PhantomData<*const u8>,
}
unsafe impl CommandPoolBuilderAlloc for StandardCommandPoolBuilder {
type Alloc = StandardCommandPoolAlloc;
#[inline]
fn inner(&self) -> &UnsafeCommandPoolAlloc {
self.inner.inner()
}
#[inline]
fn into_alloc(self) -> Self::Alloc {
self.inner
}
#[inline]
fn queue_family(&self) -> QueueFamily {
self.inner.queue_family()
}
}
unsafe impl DeviceOwned for StandardCommandPoolBuilder {
#[inline]
fn device(&self) -> &Arc<Device> {
self.inner.device()
}
}
pub struct StandardCommandPoolAlloc {
cmd: ManuallyDrop<UnsafeCommandPoolAlloc>,
pool: Arc<StandardCommandPoolPerThread>,
pool_parent: Arc<StandardCommandPool>,
level: CommandBufferLevel,
device: Arc<Device>,
}
unsafe impl Send for StandardCommandPoolAlloc {}
unsafe impl Sync for StandardCommandPoolAlloc {}
unsafe impl CommandPoolAlloc for StandardCommandPoolAlloc {
#[inline]
fn inner(&self) -> &UnsafeCommandPoolAlloc {
&*self.cmd
}
#[inline]
fn queue_family(&self) -> QueueFamily {
let queue_family_id = self.pool.pool.lock().unwrap().queue_family().id();
self.device
.physical_device()
.queue_family_by_id(queue_family_id)
.unwrap()
}
}
unsafe impl DeviceOwned for StandardCommandPoolAlloc {
#[inline]
fn device(&self) -> &Arc<Device> {
&self.device
}
}
impl Drop for StandardCommandPoolAlloc {
fn drop(&mut self) {
let cmd: UnsafeCommandPoolAlloc = unsafe { ptr::read(&*self.cmd) };
match self.level {
CommandBufferLevel::Primary => self.pool.available_primary_command_buffers.push(cmd),
CommandBufferLevel::Secondary => {
self.pool.available_secondary_command_buffers.push(cmd)
}
}
}
}
#[cfg(test)]
mod tests {
use crate::command_buffer::pool::CommandPool;
use crate::command_buffer::pool::CommandPoolBuilderAlloc;
use crate::command_buffer::pool::StandardCommandPool;
use crate::command_buffer::CommandBufferLevel;
use crate::device::Device;
use crate::VulkanObject;
use std::sync::Arc;
#[test]
fn reuse_command_buffers() {
let (device, _) = gfx_dev_and_queue!();
let queue_family = device.physical_device().queue_families().next().unwrap();
let pool = Device::standard_command_pool(&device, queue_family);
let cb_hold_weakref = pool
.allocate(CommandBufferLevel::Primary, 1)
.unwrap()
.next()
.unwrap();
let cb = pool
.allocate(CommandBufferLevel::Primary, 1)
.unwrap()
.next()
.unwrap();
let raw = cb.inner().internal_object();
drop(cb);
let cb2 = pool
.allocate(CommandBufferLevel::Primary, 1)
.unwrap()
.next()
.unwrap();
assert_eq!(raw, cb2.inner().internal_object());
}
#[test]
fn pool_kept_alive_by_allocs() {
let (device, queue) = gfx_dev_and_queue!();
let pool = Arc::new(StandardCommandPool::new(device, queue.family()));
let pool_weak = Arc::downgrade(&pool);
let cb = pool
.allocate(CommandBufferLevel::Primary, 1)
.unwrap()
.next()
.unwrap();
drop(pool);
assert!(pool_weak.upgrade().is_some());
drop(cb);
assert!(pool_weak.upgrade().is_none());
}
}