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