blob: 843218299029e298a2a93dcf6a270a4f86fac3b1 [file] [log] [blame]
// Copyright (c) 2016 The vulkano developers
// Licensed under the Apache License, Version 2.0
// <LICENSE-APACHE or
// https://www.apache.org/licenses/LICENSE-2.0> or the MIT
// license <LICENSE-MIT or https://opensource.org/licenses/MIT>,
// at your option. All files in the project carrying such
// notice may not be copied, modified, or distributed except
// according to those terms.
use crate::check_errors;
use crate::device::physical::QueueFamily;
use crate::device::Device;
use crate::device::DeviceOwned;
use crate::Error;
use crate::OomError;
use crate::Version;
use crate::VulkanObject;
use smallvec::SmallVec;
use std::error;
use std::fmt;
use std::marker::PhantomData;
use std::mem::MaybeUninit;
use std::ptr;
use std::sync::Arc;
use std::vec::IntoIter as VecIntoIter;
/// Low-level implementation of a command pool.
///
/// A command pool is always tied to a specific queue family. Command buffers allocated from a pool
/// can only be executed on the corresponding queue family.
///
/// This struct doesn't implement the `Sync` trait because Vulkan command pools are not thread
/// safe. In other words, you can only use a pool from one thread at a time.
#[derive(Debug)]
pub struct UnsafeCommandPool {
pool: ash::vk::CommandPool,
device: Arc<Device>,
// Index of the associated queue family in the physical device.
queue_family_index: u32,
// We don't want `UnsafeCommandPool` to implement Sync.
// This marker unimplements both Send and Sync, but we reimplement Send manually right under.
dummy_avoid_sync: PhantomData<*const u8>,
}
unsafe impl Send for UnsafeCommandPool {}
impl UnsafeCommandPool {
/// Creates a new pool.
///
/// The command buffers created with this pool can only be executed on queues of the given
/// family.
///
/// Setting `transient` to true is a hint to the implementation that the command buffers will
/// be short-lived.
/// Setting `reset_cb` to true means that command buffers can be reset individually.
///
/// # Panic
///
/// - Panics if the queue family doesn't belong to the same physical device as `device`.
///
pub fn new(
device: Arc<Device>,
queue_family: QueueFamily,
transient: bool,
reset_cb: bool,
) -> Result<UnsafeCommandPool, OomError> {
assert_eq!(
device.physical_device().internal_object(),
queue_family.physical_device().internal_object(),
"Device doesn't match physical device when creating a command pool"
);
let fns = device.fns();
let flags = {
let flag1 = if transient {
ash::vk::CommandPoolCreateFlags::TRANSIENT
} else {
ash::vk::CommandPoolCreateFlags::empty()
};
let flag2 = if reset_cb {
ash::vk::CommandPoolCreateFlags::RESET_COMMAND_BUFFER
} else {
ash::vk::CommandPoolCreateFlags::empty()
};
flag1 | flag2
};
let pool = unsafe {
let infos = ash::vk::CommandPoolCreateInfo {
flags: flags,
queue_family_index: queue_family.id(),
..Default::default()
};
let mut output = MaybeUninit::uninit();
check_errors(fns.v1_0.create_command_pool(
device.internal_object(),
&infos,
ptr::null(),
output.as_mut_ptr(),
))?;
output.assume_init()
};
Ok(UnsafeCommandPool {
pool: pool,
device: device.clone(),
queue_family_index: queue_family.id(),
dummy_avoid_sync: PhantomData,
})
}
/// Resets the pool, which resets all the command buffers that were allocated from it.
///
/// If `release_resources` is true, it is a hint to the implementation that it should free all
/// the memory internally allocated for this pool.
///
/// # Safety
///
/// The command buffers allocated from this pool jump to the initial state.
///
pub unsafe fn reset(&self, release_resources: bool) -> Result<(), OomError> {
let flags = if release_resources {
ash::vk::CommandPoolResetFlags::RELEASE_RESOURCES
} else {
ash::vk::CommandPoolResetFlags::empty()
};
let fns = self.device.fns();
check_errors(
fns.v1_0
.reset_command_pool(self.device.internal_object(), self.pool, flags),
)?;
Ok(())
}
/// Trims a command pool, which recycles unused internal memory from the command pool back to
/// the system.
///
/// Command buffers allocated from the pool are not affected by trimming.
///
/// This function is supported only if the `VK_KHR_maintenance1` extension was enabled at
/// device creation. Otherwise an error is returned.
/// Since this operation is purely an optimization it is legitimate to call this function and
/// simply ignore any possible error.
pub fn trim(&self) -> Result<(), CommandPoolTrimError> {
unsafe {
if !(self.device.api_version() >= Version::V1_1
|| self.device.enabled_extensions().khr_maintenance1)
{
return Err(CommandPoolTrimError::Maintenance1ExtensionNotEnabled);
}
let fns = self.device.fns();
if self.device.api_version() >= Version::V1_1 {
fns.v1_1.trim_command_pool(
self.device.internal_object(),
self.pool,
ash::vk::CommandPoolTrimFlags::empty(),
);
} else {
fns.khr_maintenance1.trim_command_pool_khr(
self.device.internal_object(),
self.pool,
ash::vk::CommandPoolTrimFlagsKHR::empty(),
);
}
Ok(())
}
}
/// Allocates `count` command buffers.
///
/// If `secondary` is true, allocates secondary command buffers. Otherwise, allocates primary
/// command buffers.
pub fn alloc_command_buffers(
&self,
secondary: bool,
count: u32,
) -> Result<UnsafeCommandPoolAllocIter, OomError> {
if count == 0 {
return Ok(UnsafeCommandPoolAllocIter {
device: self.device.clone(),
list: vec![].into_iter(),
});
}
let infos = ash::vk::CommandBufferAllocateInfo {
command_pool: self.pool,
level: if secondary {
ash::vk::CommandBufferLevel::SECONDARY
} else {
ash::vk::CommandBufferLevel::PRIMARY
},
command_buffer_count: count,
..Default::default()
};
unsafe {
let fns = self.device.fns();
let mut out = Vec::with_capacity(count as usize);
check_errors(fns.v1_0.allocate_command_buffers(
self.device.internal_object(),
&infos,
out.as_mut_ptr(),
))?;
out.set_len(count as usize);
Ok(UnsafeCommandPoolAllocIter {
device: self.device.clone(),
list: out.into_iter(),
})
}
}
/// Frees individual command buffers.
///
/// # Safety
///
/// The command buffers must have been allocated from this pool. They must not be in use.
///
pub unsafe fn free_command_buffers<I>(&self, command_buffers: I)
where
I: Iterator<Item = UnsafeCommandPoolAlloc>,
{
let command_buffers: SmallVec<[_; 4]> =
command_buffers.map(|cb| cb.command_buffer).collect();
let fns = self.device.fns();
fns.v1_0.free_command_buffers(
self.device.internal_object(),
self.pool,
command_buffers.len() as u32,
command_buffers.as_ptr(),
)
}
/// Returns the queue family on which command buffers of this pool can be executed.
#[inline]
pub fn queue_family(&self) -> QueueFamily {
self.device
.physical_device()
.queue_family_by_id(self.queue_family_index)
.unwrap()
}
}
unsafe impl DeviceOwned for UnsafeCommandPool {
#[inline]
fn device(&self) -> &Arc<Device> {
&self.device
}
}
unsafe impl VulkanObject for UnsafeCommandPool {
type Object = ash::vk::CommandPool;
#[inline]
fn internal_object(&self) -> ash::vk::CommandPool {
self.pool
}
}
impl Drop for UnsafeCommandPool {
#[inline]
fn drop(&mut self) {
unsafe {
let fns = self.device.fns();
fns.v1_0
.destroy_command_pool(self.device.internal_object(), self.pool, ptr::null());
}
}
}
/// Opaque type that represents a command buffer allocated from a pool.
pub struct UnsafeCommandPoolAlloc {
command_buffer: ash::vk::CommandBuffer,
device: Arc<Device>,
}
unsafe impl DeviceOwned for UnsafeCommandPoolAlloc {
#[inline]
fn device(&self) -> &Arc<Device> {
&self.device
}
}
unsafe impl VulkanObject for UnsafeCommandPoolAlloc {
type Object = ash::vk::CommandBuffer;
#[inline]
fn internal_object(&self) -> ash::vk::CommandBuffer {
self.command_buffer
}
}
/// Iterator for newly-allocated command buffers.
#[derive(Debug)]
pub struct UnsafeCommandPoolAllocIter {
device: Arc<Device>,
list: VecIntoIter<ash::vk::CommandBuffer>,
}
impl Iterator for UnsafeCommandPoolAllocIter {
type Item = UnsafeCommandPoolAlloc;
#[inline]
fn next(&mut self) -> Option<UnsafeCommandPoolAlloc> {
self.list
.next()
.map(|command_buffer| UnsafeCommandPoolAlloc {
command_buffer,
device: self.device.clone(),
})
}
#[inline]
fn size_hint(&self) -> (usize, Option<usize>) {
self.list.size_hint()
}
}
impl ExactSizeIterator for UnsafeCommandPoolAllocIter {}
/// Error that can happen when trimming command pools.
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub enum CommandPoolTrimError {
/// The `KHR_maintenance1` extension was not enabled.
Maintenance1ExtensionNotEnabled,
}
impl error::Error for CommandPoolTrimError {}
impl fmt::Display for CommandPoolTrimError {
#[inline]
fn fmt(&self, fmt: &mut fmt::Formatter) -> Result<(), fmt::Error> {
write!(
fmt,
"{}",
match *self {
CommandPoolTrimError::Maintenance1ExtensionNotEnabled => {
"the `KHR_maintenance1` extension was not enabled"
}
}
)
}
}
impl From<Error> for CommandPoolTrimError {
#[inline]
fn from(err: Error) -> CommandPoolTrimError {
panic!("unexpected error: {:?}", err)
}
}
#[cfg(test)]
mod tests {
use crate::command_buffer::pool::CommandPoolTrimError;
use crate::command_buffer::pool::UnsafeCommandPool;
use crate::Version;
#[test]
fn basic_create() {
let (device, queue) = gfx_dev_and_queue!();
let _ = UnsafeCommandPool::new(device, queue.family(), false, false).unwrap();
}
#[test]
fn queue_family_getter() {
let (device, queue) = gfx_dev_and_queue!();
let pool = UnsafeCommandPool::new(device, queue.family(), false, false).unwrap();
assert_eq!(pool.queue_family().id(), queue.family().id());
}
#[test]
fn panic_if_not_match_family() {
let (device, _) = gfx_dev_and_queue!();
let (_, queue) = gfx_dev_and_queue!();
assert_should_panic!(
"Device doesn't match physical device when creating a command pool",
{
let _ = UnsafeCommandPool::new(device, queue.family(), false, false);
}
);
}
#[test]
fn check_maintenance_when_trim() {
let (device, queue) = gfx_dev_and_queue!();
let pool = UnsafeCommandPool::new(device.clone(), queue.family(), false, false).unwrap();
if device.api_version() >= Version::V1_1 {
match pool.trim() {
Err(CommandPoolTrimError::Maintenance1ExtensionNotEnabled) => panic!(),
_ => (),
}
} else {
match pool.trim() {
Err(CommandPoolTrimError::Maintenance1ExtensionNotEnabled) => (),
_ => panic!(),
}
}
}
// TODO: test that trim works if VK_KHR_maintenance1 if enabled ; the test macro doesn't
// support enabling extensions yet
#[test]
fn basic_alloc() {
let (device, queue) = gfx_dev_and_queue!();
let pool = UnsafeCommandPool::new(device, queue.family(), false, false).unwrap();
let iter = pool.alloc_command_buffers(false, 12).unwrap();
assert_eq!(iter.count(), 12);
}
}