1 // Copyright (c) 2016 The vulkano developers
2 // Licensed under the Apache License, Version 2.0
3 // <LICENSE-APACHE or
4 // https://www.apache.org/licenses/LICENSE-2.0> or the MIT
5 // license <LICENSE-MIT or https://opensource.org/licenses/MIT>,
6 // at your option. All files in the project carrying such
7 // notice may not be copied, modified, or distributed except
8 // according to those terms.
9 
10 //! Memory and resource pool for recording command buffers.
11 //!
12 //! A command pool holds and manages the memory of one or more command buffers. If you destroy a
13 //! command pool, all command buffers recorded from it become invalid. This could lead to invalid
14 //! usage and unsoundness, so to ensure safety you must use a [command buffer allocator].
15 //!
16 //! [command buffer allocator]: crate::command_buffer::allocator
17 
18 use crate::{
19     command_buffer::CommandBufferLevel,
20     device::{Device, DeviceOwned},
21     macros::impl_id_counter,
22     OomError, RequiresOneOf, Version, VulkanError, VulkanObject,
23 };
24 use smallvec::SmallVec;
25 use std::{
26     cell::Cell,
27     error::Error,
28     fmt::{Display, Error as FmtError, Formatter},
29     marker::PhantomData,
30     mem::MaybeUninit,
31     num::NonZeroU64,
32     ptr,
33     sync::Arc,
34 };
35 
36 /// Represents a Vulkan command pool.
37 ///
38 /// A command pool is always tied to a specific queue family. Command buffers allocated from a pool
39 /// can only be executed on the corresponding queue family.
40 ///
41 /// This struct doesn't implement the `Sync` trait because Vulkan command pools are not thread
42 /// safe. In other words, you can only use a pool from one thread at a time.
43 #[derive(Debug)]
44 pub struct CommandPool {
45     handle: ash::vk::CommandPool,
46     device: Arc<Device>,
47     id: NonZeroU64,
48 
49     queue_family_index: u32,
50     _transient: bool,
51     _reset_command_buffer: bool,
52     // Unimplement `Sync`, as Vulkan command pools are not thread-safe.
53     _marker: PhantomData<Cell<ash::vk::CommandPool>>,
54 }
55 
56 impl CommandPool {
57     /// Creates a new `CommandPool`.
new( device: Arc<Device>, mut create_info: CommandPoolCreateInfo, ) -> Result<CommandPool, CommandPoolCreationError>58     pub fn new(
59         device: Arc<Device>,
60         mut create_info: CommandPoolCreateInfo,
61     ) -> Result<CommandPool, CommandPoolCreationError> {
62         Self::validate(&device, &mut create_info)?;
63         let handle = unsafe { Self::create(&device, &create_info)? };
64 
65         let CommandPoolCreateInfo {
66             queue_family_index,
67             transient,
68             reset_command_buffer,
69             _ne: _,
70         } = create_info;
71 
72         Ok(CommandPool {
73             handle,
74             device,
75             id: Self::next_id(),
76             queue_family_index,
77             _transient: transient,
78             _reset_command_buffer: reset_command_buffer,
79             _marker: PhantomData,
80         })
81     }
82 
83     /// Creates a new `UnsafeCommandPool` from a raw object handle.
84     ///
85     /// # Safety
86     ///
87     /// - `handle` must be a valid Vulkan object handle created from `device`.
88     /// - `create_info` must match the info used to create the object.
89     #[inline]
from_handle( device: Arc<Device>, handle: ash::vk::CommandPool, create_info: CommandPoolCreateInfo, ) -> CommandPool90     pub unsafe fn from_handle(
91         device: Arc<Device>,
92         handle: ash::vk::CommandPool,
93         create_info: CommandPoolCreateInfo,
94     ) -> CommandPool {
95         let CommandPoolCreateInfo {
96             queue_family_index,
97             transient,
98             reset_command_buffer,
99             _ne: _,
100         } = create_info;
101 
102         CommandPool {
103             handle,
104             device,
105             id: Self::next_id(),
106             queue_family_index,
107             _transient: transient,
108             _reset_command_buffer: reset_command_buffer,
109             _marker: PhantomData,
110         }
111     }
112 
validate( device: &Device, create_info: &mut CommandPoolCreateInfo, ) -> Result<(), CommandPoolCreationError>113     fn validate(
114         device: &Device,
115         create_info: &mut CommandPoolCreateInfo,
116     ) -> Result<(), CommandPoolCreationError> {
117         let &mut CommandPoolCreateInfo {
118             queue_family_index,
119             transient: _,
120             reset_command_buffer: _,
121             _ne: _,
122         } = create_info;
123 
124         // VUID-vkCreateCommandPool-queueFamilyIndex-01937
125         if queue_family_index >= device.physical_device().queue_family_properties().len() as u32 {
126             return Err(CommandPoolCreationError::QueueFamilyIndexOutOfRange {
127                 queue_family_index,
128                 queue_family_count: device.physical_device().queue_family_properties().len() as u32,
129             });
130         }
131 
132         Ok(())
133     }
134 
create( device: &Device, create_info: &CommandPoolCreateInfo, ) -> Result<ash::vk::CommandPool, CommandPoolCreationError>135     unsafe fn create(
136         device: &Device,
137         create_info: &CommandPoolCreateInfo,
138     ) -> Result<ash::vk::CommandPool, CommandPoolCreationError> {
139         let &CommandPoolCreateInfo {
140             queue_family_index,
141             transient,
142             reset_command_buffer,
143             _ne: _,
144         } = create_info;
145 
146         let mut flags = ash::vk::CommandPoolCreateFlags::empty();
147 
148         if transient {
149             flags |= ash::vk::CommandPoolCreateFlags::TRANSIENT;
150         }
151 
152         if reset_command_buffer {
153             flags |= ash::vk::CommandPoolCreateFlags::RESET_COMMAND_BUFFER;
154         }
155 
156         let create_info = ash::vk::CommandPoolCreateInfo {
157             flags,
158             queue_family_index,
159             ..Default::default()
160         };
161 
162         let handle = {
163             let fns = device.fns();
164             let mut output = MaybeUninit::uninit();
165             (fns.v1_0.create_command_pool)(
166                 device.handle(),
167                 &create_info,
168                 ptr::null(),
169                 output.as_mut_ptr(),
170             )
171             .result()
172             .map_err(VulkanError::from)?;
173             output.assume_init()
174         };
175 
176         Ok(handle)
177     }
178 
179     /// Resets the pool, which resets all the command buffers that were allocated from it.
180     ///
181     /// If `release_resources` is true, it is a hint to the implementation that it should free all
182     /// the memory internally allocated for this pool.
183     ///
184     /// # Safety
185     ///
186     /// - The command buffers allocated from this pool jump to the initial state.
187     #[inline]
reset(&self, release_resources: bool) -> Result<(), OomError>188     pub unsafe fn reset(&self, release_resources: bool) -> Result<(), OomError> {
189         let flags = if release_resources {
190             ash::vk::CommandPoolResetFlags::RELEASE_RESOURCES
191         } else {
192             ash::vk::CommandPoolResetFlags::empty()
193         };
194 
195         let fns = self.device.fns();
196         (fns.v1_0.reset_command_pool)(self.device.handle(), self.handle, flags)
197             .result()
198             .map_err(VulkanError::from)?;
199 
200         Ok(())
201     }
202 
203     /// Allocates command buffers.
204     #[inline]
allocate_command_buffers( &self, allocate_info: CommandBufferAllocateInfo, ) -> Result<impl ExactSizeIterator<Item = CommandPoolAlloc>, OomError>205     pub fn allocate_command_buffers(
206         &self,
207         allocate_info: CommandBufferAllocateInfo,
208     ) -> Result<impl ExactSizeIterator<Item = CommandPoolAlloc>, OomError> {
209         let CommandBufferAllocateInfo {
210             level,
211             command_buffer_count,
212             _ne: _,
213         } = allocate_info;
214 
215         // VUID-vkAllocateCommandBuffers-pAllocateInfo::commandBufferCount-arraylength
216         let out = if command_buffer_count == 0 {
217             vec![]
218         } else {
219             let allocate_info = ash::vk::CommandBufferAllocateInfo {
220                 command_pool: self.handle,
221                 level: level.into(),
222                 command_buffer_count,
223                 ..Default::default()
224             };
225 
226             unsafe {
227                 let fns = self.device.fns();
228                 let mut out = Vec::with_capacity(command_buffer_count as usize);
229                 (fns.v1_0.allocate_command_buffers)(
230                     self.device.handle(),
231                     &allocate_info,
232                     out.as_mut_ptr(),
233                 )
234                 .result()
235                 .map_err(VulkanError::from)?;
236                 out.set_len(command_buffer_count as usize);
237                 out
238             }
239         };
240 
241         let device = self.device.clone();
242 
243         Ok(out.into_iter().map(move |command_buffer| CommandPoolAlloc {
244             handle: command_buffer,
245             device: device.clone(),
246             id: CommandPoolAlloc::next_id(),
247             level,
248         }))
249     }
250 
251     /// Frees individual command buffers.
252     ///
253     /// # Safety
254     ///
255     /// - The `command_buffers` must have been allocated from this pool.
256     /// - The `command_buffers` must not be in the pending state.
free_command_buffers( &self, command_buffers: impl IntoIterator<Item = CommandPoolAlloc>, )257     pub unsafe fn free_command_buffers(
258         &self,
259         command_buffers: impl IntoIterator<Item = CommandPoolAlloc>,
260     ) {
261         let command_buffers: SmallVec<[_; 4]> =
262             command_buffers.into_iter().map(|cb| cb.handle).collect();
263         let fns = self.device.fns();
264         (fns.v1_0.free_command_buffers)(
265             self.device.handle(),
266             self.handle,
267             command_buffers.len() as u32,
268             command_buffers.as_ptr(),
269         )
270     }
271 
272     /// Trims a command pool, which recycles unused internal memory from the command pool back to
273     /// the system.
274     ///
275     /// Command buffers allocated from the pool are not affected by trimming.
276     ///
277     /// This function is supported only if the
278     /// [`khr_maintenance1`](crate::device::DeviceExtensions::khr_maintenance1) extension is
279     /// enabled on the device. Otherwise an error is returned.
280     /// Since this operation is purely an optimization it is legitimate to call this function and
281     /// simply ignore any possible error.
282     #[inline]
trim(&self) -> Result<(), CommandPoolTrimError>283     pub fn trim(&self) -> Result<(), CommandPoolTrimError> {
284         if !(self.device.api_version() >= Version::V1_1
285             || self.device.enabled_extensions().khr_maintenance1)
286         {
287             return Err(CommandPoolTrimError::RequirementNotMet {
288                 required_for: "`CommandPool::trim`",
289                 requires_one_of: RequiresOneOf {
290                     api_version: Some(Version::V1_1),
291                     device_extensions: &["khr_maintenance1"],
292                     ..Default::default()
293                 },
294             });
295         }
296 
297         unsafe {
298             let fns = self.device.fns();
299 
300             if self.device.api_version() >= Version::V1_1 {
301                 (fns.v1_1.trim_command_pool)(
302                     self.device.handle(),
303                     self.handle,
304                     ash::vk::CommandPoolTrimFlags::empty(),
305                 );
306             } else {
307                 (fns.khr_maintenance1.trim_command_pool_khr)(
308                     self.device.handle(),
309                     self.handle,
310                     ash::vk::CommandPoolTrimFlagsKHR::empty(),
311                 );
312             }
313 
314             Ok(())
315         }
316     }
317 
318     /// Returns the queue family on which command buffers of this pool can be executed.
319     #[inline]
queue_family_index(&self) -> u32320     pub fn queue_family_index(&self) -> u32 {
321         self.queue_family_index
322     }
323 }
324 
325 impl Drop for CommandPool {
326     #[inline]
drop(&mut self)327     fn drop(&mut self) {
328         unsafe {
329             let fns = self.device.fns();
330             (fns.v1_0.destroy_command_pool)(self.device.handle(), self.handle, ptr::null());
331         }
332     }
333 }
334 
335 unsafe impl VulkanObject for CommandPool {
336     type Handle = ash::vk::CommandPool;
337 
338     #[inline]
handle(&self) -> Self::Handle339     fn handle(&self) -> Self::Handle {
340         self.handle
341     }
342 }
343 
344 unsafe impl DeviceOwned for CommandPool {
345     #[inline]
device(&self) -> &Arc<Device>346     fn device(&self) -> &Arc<Device> {
347         &self.device
348     }
349 }
350 
351 impl_id_counter!(CommandPool);
352 
353 /// Error that can happen when creating a `CommandPool`.
354 #[derive(Clone, Copy, Debug, PartialEq, Eq)]
355 pub enum CommandPoolCreationError {
356     /// Not enough memory.
357     OomError(OomError),
358 
359     /// The provided `queue_family_index` was not less than the number of queue families in the
360     /// physical device.
361     QueueFamilyIndexOutOfRange {
362         queue_family_index: u32,
363         queue_family_count: u32,
364     },
365 }
366 
367 impl Error for CommandPoolCreationError {
source(&self) -> Option<&(dyn Error + 'static)>368     fn source(&self) -> Option<&(dyn Error + 'static)> {
369         match self {
370             Self::OomError(err) => Some(err),
371             _ => None,
372         }
373     }
374 }
375 
376 impl Display for CommandPoolCreationError {
fmt(&self, f: &mut Formatter<'_>) -> Result<(), FmtError>377     fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), FmtError> {
378         match self {
379             Self::OomError(_) => write!(f, "not enough memory",),
380             Self::QueueFamilyIndexOutOfRange {
381                 queue_family_index,
382                 queue_family_count,
383             } => write!(
384                 f,
385                 "the provided `queue_family_index` ({}) was not less than the number of queue \
386                 families in the physical device ({})",
387                 queue_family_index, queue_family_count,
388             ),
389         }
390     }
391 }
392 
393 impl From<VulkanError> for CommandPoolCreationError {
from(err: VulkanError) -> Self394     fn from(err: VulkanError) -> Self {
395         match err {
396             err @ VulkanError::OutOfHostMemory => Self::OomError(OomError::from(err)),
397             _ => panic!("unexpected error: {:?}", err),
398         }
399     }
400 }
401 
402 /// Parameters to create an `CommandPool`.
403 #[derive(Clone, Debug)]
404 pub struct CommandPoolCreateInfo {
405     /// The index of the queue family that this pool is created for. All command buffers allocated
406     /// from this pool must be submitted on a queue belonging to that family.
407     ///
408     /// The default value is `u32::MAX`, which must be overridden.
409     pub queue_family_index: u32,
410 
411     /// A hint to the implementation that the command buffers allocated from this pool will be
412     /// short-lived.
413     ///
414     /// The default value is `false`.
415     pub transient: bool,
416 
417     /// Whether the command buffers allocated from this pool can be reset individually.
418     ///
419     /// The default value is `false`.
420     pub reset_command_buffer: bool,
421 
422     pub _ne: crate::NonExhaustive,
423 }
424 
425 impl Default for CommandPoolCreateInfo {
426     #[inline]
default() -> Self427     fn default() -> Self {
428         Self {
429             queue_family_index: u32::MAX,
430             transient: false,
431             reset_command_buffer: false,
432             _ne: crate::NonExhaustive(()),
433         }
434     }
435 }
436 
437 /// Parameters to allocate an `UnsafeCommandPoolAlloc`.
438 #[derive(Clone, Debug)]
439 pub struct CommandBufferAllocateInfo {
440     /// The level of command buffer to allocate.
441     ///
442     /// The default value is [`CommandBufferLevel::Primary`].
443     pub level: CommandBufferLevel,
444 
445     /// The number of command buffers to allocate.
446     ///
447     /// The default value is `1`.
448     pub command_buffer_count: u32,
449 
450     pub _ne: crate::NonExhaustive,
451 }
452 
453 impl Default for CommandBufferAllocateInfo {
454     #[inline]
default() -> Self455     fn default() -> Self {
456         Self {
457             level: CommandBufferLevel::Primary,
458             command_buffer_count: 1,
459             _ne: crate::NonExhaustive(()),
460         }
461     }
462 }
463 
464 /// Opaque type that represents a command buffer allocated from a pool.
465 #[derive(Debug)]
466 pub struct CommandPoolAlloc {
467     handle: ash::vk::CommandBuffer,
468     device: Arc<Device>,
469     id: NonZeroU64,
470     level: CommandBufferLevel,
471 }
472 
473 impl CommandPoolAlloc {
474     /// Returns the level of the command buffer.
475     #[inline]
level(&self) -> CommandBufferLevel476     pub fn level(&self) -> CommandBufferLevel {
477         self.level
478     }
479 }
480 
481 unsafe impl VulkanObject for CommandPoolAlloc {
482     type Handle = ash::vk::CommandBuffer;
483 
484     #[inline]
handle(&self) -> Self::Handle485     fn handle(&self) -> Self::Handle {
486         self.handle
487     }
488 }
489 
490 unsafe impl DeviceOwned for CommandPoolAlloc {
491     #[inline]
device(&self) -> &Arc<Device>492     fn device(&self) -> &Arc<Device> {
493         &self.device
494     }
495 }
496 
497 impl_id_counter!(CommandPoolAlloc);
498 
499 /// Error that can happen when trimming command pools.
500 #[derive(Copy, Clone, Debug, PartialEq, Eq)]
501 pub enum CommandPoolTrimError {
502     RequirementNotMet {
503         required_for: &'static str,
504         requires_one_of: RequiresOneOf,
505     },
506 }
507 
508 impl Error for CommandPoolTrimError {}
509 
510 impl Display for CommandPoolTrimError {
fmt(&self, f: &mut Formatter<'_>) -> Result<(), FmtError>511     fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), FmtError> {
512         match self {
513             Self::RequirementNotMet {
514                 required_for,
515                 requires_one_of,
516             } => write!(
517                 f,
518                 "a requirement was not met for: {}; requires one of: {}",
519                 required_for, requires_one_of,
520             ),
521         }
522     }
523 }
524 
525 impl From<VulkanError> for CommandPoolTrimError {
from(err: VulkanError) -> CommandPoolTrimError526     fn from(err: VulkanError) -> CommandPoolTrimError {
527         panic!("unexpected error: {:?}", err)
528     }
529 }
530 
531 #[cfg(test)]
532 mod tests {
533     use super::{
534         CommandPool, CommandPoolCreateInfo, CommandPoolCreationError, CommandPoolTrimError,
535     };
536     use crate::{
537         command_buffer::{pool::CommandBufferAllocateInfo, CommandBufferLevel},
538         RequiresOneOf, Version,
539     };
540 
541     #[test]
basic_create()542     fn basic_create() {
543         let (device, queue) = gfx_dev_and_queue!();
544         let _ = CommandPool::new(
545             device,
546             CommandPoolCreateInfo {
547                 queue_family_index: queue.queue_family_index(),
548                 ..Default::default()
549             },
550         )
551         .unwrap();
552     }
553 
554     #[test]
queue_family_getter()555     fn queue_family_getter() {
556         let (device, queue) = gfx_dev_and_queue!();
557         let pool = CommandPool::new(
558             device,
559             CommandPoolCreateInfo {
560                 queue_family_index: queue.queue_family_index(),
561                 ..Default::default()
562             },
563         )
564         .unwrap();
565         assert_eq!(pool.queue_family_index(), queue.queue_family_index());
566     }
567 
568     #[test]
check_queue_family_too_high()569     fn check_queue_family_too_high() {
570         let (device, _) = gfx_dev_and_queue!();
571 
572         match CommandPool::new(
573             device,
574             CommandPoolCreateInfo {
575                 ..Default::default()
576             },
577         ) {
578             Err(CommandPoolCreationError::QueueFamilyIndexOutOfRange { .. }) => (),
579             _ => panic!(),
580         }
581     }
582 
583     #[test]
check_maintenance_when_trim()584     fn check_maintenance_when_trim() {
585         let (device, queue) = gfx_dev_and_queue!();
586         let pool = CommandPool::new(
587             device.clone(),
588             CommandPoolCreateInfo {
589                 queue_family_index: queue.queue_family_index(),
590                 ..Default::default()
591             },
592         )
593         .unwrap();
594 
595         if device.api_version() >= Version::V1_1 {
596             if matches!(
597                 pool.trim(),
598                 Err(CommandPoolTrimError::RequirementNotMet {
599                     requires_one_of: RequiresOneOf {
600                         device_extensions,
601                         ..
602                     }, ..
603                 }) if device_extensions.contains(&"khr_maintenance1")
604             ) {
605                 panic!()
606             }
607         } else {
608             if !matches!(
609                 pool.trim(),
610                 Err(CommandPoolTrimError::RequirementNotMet {
611                     requires_one_of: RequiresOneOf {
612                         device_extensions,
613                         ..
614                     }, ..
615                 }) if device_extensions.contains(&"khr_maintenance1")
616             ) {
617                 panic!()
618             }
619         }
620     }
621 
622     // TODO: test that trim works if VK_KHR_maintenance1 if enabled ; the test macro doesn't
623     //       support enabling extensions yet
624 
625     #[test]
basic_alloc()626     fn basic_alloc() {
627         let (device, queue) = gfx_dev_and_queue!();
628         let pool = CommandPool::new(
629             device,
630             CommandPoolCreateInfo {
631                 queue_family_index: queue.queue_family_index(),
632                 ..Default::default()
633             },
634         )
635         .unwrap();
636         let iter = pool
637             .allocate_command_buffers(CommandBufferAllocateInfo {
638                 level: CommandBufferLevel::Primary,
639                 command_buffer_count: 12,
640                 ..Default::default()
641             })
642             .unwrap();
643         assert_eq!(iter.count(), 12);
644     }
645 }
646