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 pub use self::commands::SyncCommandBufferBuilderBindDescriptorSets; 11 pub use self::commands::SyncCommandBufferBuilderBindVertexBuffer; 12 pub use self::commands::SyncCommandBufferBuilderExecuteCommands; 13 use super::Command; 14 use super::ResourceFinalState; 15 use super::ResourceKey; 16 use super::ResourceLocation; 17 use super::SyncCommandBuffer; 18 use crate::buffer::BufferAccess; 19 use crate::command_buffer::pool::UnsafeCommandPoolAlloc; 20 use crate::command_buffer::sys::UnsafeCommandBufferBuilder; 21 use crate::command_buffer::sys::UnsafeCommandBufferBuilderPipelineBarrier; 22 use crate::command_buffer::CommandBufferExecError; 23 use crate::command_buffer::CommandBufferLevel; 24 use crate::command_buffer::CommandBufferUsage; 25 use crate::command_buffer::ImageUninitializedSafe; 26 use crate::descriptor_set::DescriptorSet; 27 use crate::device::Device; 28 use crate::device::DeviceOwned; 29 use crate::image::ImageLayout; 30 use crate::pipeline::{ComputePipelineAbstract, GraphicsPipelineAbstract, PipelineBindPoint}; 31 use crate::render_pass::FramebufferAbstract; 32 use crate::sync::AccessFlags; 33 use crate::sync::PipelineMemoryAccess; 34 use crate::sync::PipelineStages; 35 use crate::OomError; 36 use fnv::FnvHashMap; 37 use std::borrow::Cow; 38 use std::collections::hash_map::Entry; 39 use std::error; 40 use std::fmt; 41 use std::sync::Arc; 42 43 #[path = "commands.rs"] 44 mod commands; 45 46 /// Wrapper around `UnsafeCommandBufferBuilder` that handles synchronization for you. 47 /// 48 /// Each method of the `UnsafeCommandBufferBuilder` has an equivalent in this wrapper, except 49 /// for `pipeline_layout` which is automatically handled. This wrapper automatically builds 50 /// pipeline barriers, keeps used resources alive and implements the `CommandBuffer` trait. 51 /// 52 /// Since the implementation needs to cache commands in a `Vec`, most methods have additional 53 /// `Send + Sync + 'static` trait requirements on their generics. 54 /// 55 /// If this builder finds out that a command isn't valid because of synchronization reasons (eg. 56 /// trying to copy from a buffer to an image which share the same memory), then an error is 57 /// returned. 58 /// Note that all methods are still unsafe, because this builder doesn't check the validity of 59 /// the commands except for synchronization purposes. The builder may panic if you pass invalid 60 /// commands. 61 pub struct SyncCommandBufferBuilder { 62 // The actual Vulkan command buffer builder. 63 inner: UnsafeCommandBufferBuilder, 64 65 // Stores all the commands that were added to the sync builder. Some of them are maybe not 66 // submitted to the inner builder yet. 67 // Each command owns the resources it uses (buffers, images, pipelines, descriptor sets etc.), 68 // references to any of these must be indirect in the form of a command index + resource id. 69 commands: Vec<Arc<dyn Command + Send + Sync>>, 70 71 // Prototype for the pipeline barrier that must be submitted before flushing the commands 72 // in `commands`. 73 pending_barrier: UnsafeCommandBufferBuilderPipelineBarrier, 74 75 // Locations within commands that pipeline barriers were inserted. For debugging purposes. 76 // TODO: present only in cfg(debug_assertions)? 77 barriers: Vec<usize>, 78 79 // Only the commands before `first_unflushed` have already been sent to the inner 80 // `UnsafeCommandBufferBuilder`. 81 first_unflushed: usize, 82 83 // If we're currently inside a render pass, contains the index of the `CmdBeginRenderPass` 84 // command. 85 latest_render_pass_enter: Option<usize>, 86 87 // Stores the current state of buffers and images that are in use by the command buffer. 88 resources: FnvHashMap<ResourceKey, ResourceState>, 89 90 // Resources and their accesses. Used for executing secondary command buffers in a primary. 91 buffers: Vec<(ResourceLocation, PipelineMemoryAccess)>, 92 images: Vec<( 93 ResourceLocation, 94 PipelineMemoryAccess, 95 ImageLayout, 96 ImageLayout, 97 ImageUninitializedSafe, 98 )>, 99 100 // State of bindings. 101 bindings: BindingState, 102 103 // `true` if the builder has been put in an inconsistent state. This happens when 104 // `append_command` throws an error, because some changes to the internal state have already 105 // been made at that point and can't be reverted. 106 // TODO: throw the error in `append_command` _before_ any state changes are made, 107 // so that this is no longer needed. 108 is_poisoned: bool, 109 110 // True if we're a secondary command buffer. 111 is_secondary: bool, 112 } 113 114 impl SyncCommandBufferBuilder { 115 /// Builds a new `SyncCommandBufferBuilder`. The parameters are the same as the 116 /// `UnsafeCommandBufferBuilder::new` function. 117 /// 118 /// # Safety 119 /// 120 /// See `UnsafeCommandBufferBuilder::new()`. new<F>( pool_alloc: &UnsafeCommandPoolAlloc, level: CommandBufferLevel<F>, usage: CommandBufferUsage, ) -> Result<SyncCommandBufferBuilder, OomError> where F: FramebufferAbstract,121 pub unsafe fn new<F>( 122 pool_alloc: &UnsafeCommandPoolAlloc, 123 level: CommandBufferLevel<F>, 124 usage: CommandBufferUsage, 125 ) -> Result<SyncCommandBufferBuilder, OomError> 126 where 127 F: FramebufferAbstract, 128 { 129 let (is_secondary, inside_render_pass) = match level { 130 CommandBufferLevel::Primary => (false, false), 131 CommandBufferLevel::Secondary(ref inheritance) => { 132 (true, inheritance.render_pass.is_some()) 133 } 134 }; 135 136 let cmd = UnsafeCommandBufferBuilder::new(pool_alloc, level, usage)?; 137 Ok(SyncCommandBufferBuilder::from_unsafe_cmd( 138 cmd, 139 is_secondary, 140 inside_render_pass, 141 )) 142 } 143 144 /// Builds a `SyncCommandBufferBuilder` from an existing `UnsafeCommandBufferBuilder`. 145 /// 146 /// # Safety 147 /// 148 /// See `UnsafeCommandBufferBuilder::new()`. 149 /// 150 /// In addition to this, the `UnsafeCommandBufferBuilder` should be empty. If it isn't, then 151 /// you must take into account the fact that the `SyncCommandBufferBuilder` won't be aware of 152 /// any existing resource usage. 153 #[inline] from_unsafe_cmd( cmd: UnsafeCommandBufferBuilder, is_secondary: bool, inside_render_pass: bool, ) -> SyncCommandBufferBuilder154 pub unsafe fn from_unsafe_cmd( 155 cmd: UnsafeCommandBufferBuilder, 156 is_secondary: bool, 157 inside_render_pass: bool, 158 ) -> SyncCommandBufferBuilder { 159 let latest_render_pass_enter = if inside_render_pass { Some(0) } else { None }; 160 161 SyncCommandBufferBuilder { 162 inner: cmd, 163 commands: Vec::new(), 164 pending_barrier: UnsafeCommandBufferBuilderPipelineBarrier::new(), 165 barriers: Vec::new(), 166 first_unflushed: 0, 167 latest_render_pass_enter, 168 resources: FnvHashMap::default(), 169 buffers: Vec::new(), 170 images: Vec::new(), 171 bindings: Default::default(), 172 is_poisoned: false, 173 is_secondary, 174 } 175 } 176 177 // Adds a command to be processed by the builder. 178 // 179 // The `resources` argument should contain each buffer or image used by the command. 180 // The function will take care of handling the pipeline barrier or flushing. 181 // 182 // - The index of the resource within the `resources` slice maps to the resource accessed 183 // through `Command::buffer(..)` or `Command::image(..)`. 184 // - `PipelineMemoryAccess` must match the way the resource has been used. 185 // - `start_layout` and `end_layout` designate the image layout that the image is expected to be 186 // in when the command starts, and the image layout that the image will be transitioned to 187 // during the command. When it comes to buffers, you should pass `Undefined` for both. 188 #[inline] append_command<C>( &mut self, command: C, resources: &[( KeyTy, Option<( PipelineMemoryAccess, ImageLayout, ImageLayout, ImageUninitializedSafe, )>, )], ) -> Result<(), SyncCommandBufferBuilderError> where C: Command + Send + Sync + 'static,189 fn append_command<C>( 190 &mut self, 191 command: C, 192 resources: &[( 193 KeyTy, 194 Option<( 195 PipelineMemoryAccess, 196 ImageLayout, 197 ImageLayout, 198 ImageUninitializedSafe, 199 )>, 200 )], 201 ) -> Result<(), SyncCommandBufferBuilderError> 202 where 203 C: Command + Send + Sync + 'static, 204 { 205 // TODO: see comment for the `is_poisoned` member in the struct 206 assert!( 207 !self.is_poisoned, 208 "The builder has been put in an inconsistent state by a previous error" 209 ); 210 211 // Note that we don't submit the command to the inner command buffer yet. 212 let (latest_command_id, end) = { 213 self.commands.push(Arc::new(command)); 214 let latest_command_id = self.commands.len() - 1; 215 let end = self.latest_render_pass_enter.unwrap_or(latest_command_id); 216 (latest_command_id, end) 217 }; 218 let mut last_cmd_buffer = 0; 219 let mut last_cmd_image = 0; 220 221 for &(resource_ty, resource) in resources { 222 if let Some((memory, start_layout, end_layout, image_uninitialized_safe)) = resource { 223 // Anti-dumbness checks. 224 debug_assert!(memory.exclusive || start_layout == end_layout); 225 debug_assert!(memory.access.is_compatible_with(&memory.stages)); 226 debug_assert!(resource_ty != KeyTy::Image || end_layout != ImageLayout::Undefined); 227 debug_assert!( 228 resource_ty != KeyTy::Buffer || start_layout == ImageLayout::Undefined 229 ); 230 debug_assert!(resource_ty != KeyTy::Buffer || end_layout == ImageLayout::Undefined); 231 debug_assert_ne!(end_layout, ImageLayout::Preinitialized); 232 233 let (resource_key, resource_index) = match resource_ty { 234 KeyTy::Buffer => { 235 let buffer = self.commands[latest_command_id].buffer(last_cmd_buffer); 236 (ResourceKey::from(buffer), last_cmd_buffer) 237 } 238 KeyTy::Image => { 239 let image = self.commands[latest_command_id].image(last_cmd_image); 240 (ResourceKey::from(image), last_cmd_image) 241 } 242 }; 243 244 match self.resources.entry(resource_key) { 245 // Situation where this resource was used before in this command buffer. 246 Entry::Occupied(mut entry) => { 247 // `collision_cmd_ids` contains the IDs of the commands that we are potentially 248 // colliding with. 249 let collision_cmd_ids = &entry.get().command_ids; 250 debug_assert!(collision_cmd_ids.iter().all(|id| *id <= latest_command_id)); 251 252 let entry_key_resource_index = entry.get().resource_index; 253 254 // Find out if we have a collision with the pending commands. 255 if memory.exclusive 256 || entry.get().memory.exclusive 257 || entry.get().current_layout != start_layout 258 { 259 // Collision found between `latest_command_id` and `collision_cmd_id`. 260 261 // We now want to modify the current pipeline barrier in order to handle the 262 // collision. But since the pipeline barrier is going to be submitted before 263 // the flushed commands, it would be a mistake if `collision_cmd_id` hasn't 264 // been flushed yet. 265 let first_unflushed_cmd_id = self.first_unflushed; 266 267 if collision_cmd_ids 268 .iter() 269 .any(|command_id| *command_id >= first_unflushed_cmd_id) 270 || entry.get().current_layout != start_layout 271 { 272 unsafe { 273 // Flush the pending barrier. 274 self.inner.pipeline_barrier(&self.pending_barrier); 275 self.pending_barrier = 276 UnsafeCommandBufferBuilderPipelineBarrier::new(); 277 278 // Flush the commands if possible, or return an error if not possible. 279 { 280 let start = self.first_unflushed; 281 self.barriers.push(start); // Track inserted barriers 282 283 if let Some(collision_cmd_id) = collision_cmd_ids 284 .iter() 285 .find(|command_id| **command_id >= end) 286 { 287 // TODO: see comment for the `is_poisoned` member in the struct 288 self.is_poisoned = true; 289 290 let cmd1 = &self.commands[*collision_cmd_id]; 291 let cmd2 = &self.commands[latest_command_id]; 292 293 return Err(SyncCommandBufferBuilderError::Conflict { 294 command1_name: cmd1.name(), 295 command1_param: match resource_ty { 296 KeyTy::Buffer => { 297 cmd1.buffer_name(entry_key_resource_index) 298 } 299 KeyTy::Image => { 300 cmd1.image_name(entry_key_resource_index) 301 } 302 }, 303 command1_offset: *collision_cmd_id, 304 305 command2_name: cmd2.name(), 306 command2_param: match resource_ty { 307 KeyTy::Buffer => { 308 cmd2.buffer_name(resource_index) 309 } 310 KeyTy::Image => cmd2.image_name(resource_index), 311 }, 312 command2_offset: latest_command_id, 313 }); 314 } 315 for command in &mut self.commands[start..end] { 316 command.send(&mut self.inner); 317 } 318 self.first_unflushed = end; 319 } 320 } 321 } 322 323 entry.get_mut().command_ids.push(latest_command_id); 324 let entry = entry.into_mut(); 325 326 // Modify the pipeline barrier to handle the collision. 327 unsafe { 328 match resource_ty { 329 KeyTy::Buffer => { 330 let buf = 331 self.commands[latest_command_id].buffer(resource_index); 332 333 let b = &mut self.pending_barrier; 334 b.add_buffer_memory_barrier( 335 buf, 336 entry.memory.stages, 337 entry.memory.access, 338 memory.stages, 339 memory.access, 340 true, 341 None, 342 0, 343 buf.size(), 344 ); 345 } 346 347 KeyTy::Image => { 348 let img = 349 self.commands[latest_command_id].image(resource_index); 350 351 let b = &mut self.pending_barrier; 352 b.add_image_memory_barrier( 353 img, 354 img.current_miplevels_access(), 355 img.current_layer_levels_access(), 356 entry.memory.stages, 357 entry.memory.access, 358 memory.stages, 359 memory.access, 360 true, 361 None, 362 entry.current_layout, 363 start_layout, 364 ); 365 } 366 }; 367 } 368 369 // Update state. 370 entry.memory = memory; 371 entry.exclusive_any = true; 372 if memory.exclusive || end_layout != ImageLayout::Undefined { 373 // Only modify the layout in case of a write, because buffer operations 374 // pass `Undefined` for the layout. While a buffer write *must* set the 375 // layout to `Undefined`, a buffer read must not touch it. 376 entry.current_layout = end_layout; 377 } 378 } else { 379 // There is no collision. Simply merge the stages and accesses. 380 // TODO: what about simplifying the newly-constructed stages/accesses? 381 // this would simplify the job of the driver, but is it worth it? 382 let entry = entry.into_mut(); 383 entry.memory.stages |= memory.stages; 384 entry.memory.access |= memory.access; 385 } 386 } 387 388 // Situation where this is the first time we use this resource in this command buffer. 389 Entry::Vacant(entry) => { 390 // We need to perform some tweaks if the initial layout requirement of the image 391 // is different from the first layout usage. 392 let mut actually_exclusive = memory.exclusive; 393 let mut actual_start_layout = start_layout; 394 395 if !self.is_secondary 396 && resource_ty == KeyTy::Image 397 && start_layout != ImageLayout::Undefined 398 && start_layout != ImageLayout::Preinitialized 399 { 400 let img = self.commands[latest_command_id].image(resource_index); 401 let initial_layout_requirement = img.initial_layout_requirement(); 402 403 // Checks if the image is initialized and transitions it 404 // if it isn't 405 let is_layout_initialized = img.is_layout_initialized(); 406 407 if initial_layout_requirement != start_layout || !is_layout_initialized 408 { 409 // Note that we transition from `bottom_of_pipe`, which means that we 410 // wait for all the previous commands to be entirely finished. This is 411 // suboptimal, but: 412 // 413 // - If we're at the start of the command buffer we have no choice anyway, 414 // because we have no knowledge about what comes before. 415 // - If we're in the middle of the command buffer, this pipeline is going 416 // to be merged with an existing barrier. While it may still be 417 // suboptimal in some cases, in the general situation it will be ok. 418 // 419 unsafe { 420 let from_layout = if is_layout_initialized { 421 actually_exclusive = true; 422 initial_layout_requirement 423 } else { 424 if img.preinitialized_layout() { 425 ImageLayout::Preinitialized 426 } else { 427 ImageLayout::Undefined 428 } 429 }; 430 if initial_layout_requirement != start_layout { 431 actual_start_layout = initial_layout_requirement; 432 } 433 let b = &mut self.pending_barrier; 434 b.add_image_memory_barrier( 435 img, 436 img.current_miplevels_access(), 437 img.current_layer_levels_access(), 438 PipelineStages { 439 bottom_of_pipe: true, 440 ..PipelineStages::none() 441 }, 442 AccessFlags::none(), 443 memory.stages, 444 memory.access, 445 true, 446 None, 447 from_layout, 448 start_layout, 449 ); 450 img.layout_initialized(); 451 } 452 } 453 } 454 455 entry.insert(ResourceState { 456 command_ids: vec![latest_command_id], 457 resource_index, 458 459 memory: PipelineMemoryAccess { 460 stages: memory.stages, 461 access: memory.access, 462 exclusive: actually_exclusive, 463 }, 464 exclusive_any: actually_exclusive, 465 initial_layout: actual_start_layout, 466 current_layout: end_layout, // TODO: what if we reach the end with Undefined? that's not correct? 467 image_uninitialized_safe, 468 }); 469 } 470 } 471 472 // Add the resources to the lists 473 // TODO: Perhaps any barriers for a resource in the secondary command buffer will "protect" 474 // its accesses so the primary needs less strict barriers. 475 // Less barriers is more efficient, so worth investigating! 476 let location = ResourceLocation { 477 command_id: latest_command_id, 478 resource_index, 479 }; 480 481 match resource_ty { 482 KeyTy::Buffer => { 483 self.buffers.push((location, memory)); 484 last_cmd_buffer += 1; 485 } 486 KeyTy::Image => { 487 self.images.push(( 488 location, 489 memory, 490 start_layout, 491 end_layout, 492 image_uninitialized_safe, 493 )); 494 last_cmd_image += 1; 495 } 496 } 497 } else { 498 match resource_ty { 499 KeyTy::Buffer => { 500 last_cmd_buffer += 1; 501 } 502 KeyTy::Image => { 503 last_cmd_image += 1; 504 } 505 } 506 } 507 } 508 509 Ok(()) 510 } 511 512 /// Builds the command buffer and turns it into a `SyncCommandBuffer`. 513 #[inline] build(mut self) -> Result<SyncCommandBuffer, OomError>514 pub fn build(mut self) -> Result<SyncCommandBuffer, OomError> { 515 // TODO: see comment for the `is_poisoned` member in the struct 516 assert!( 517 !self.is_poisoned, 518 "The builder has been put in an inconsistent state by a previous error" 519 ); 520 521 debug_assert!(self.latest_render_pass_enter.is_none() || self.pending_barrier.is_empty()); 522 523 // The commands that haven't been sent to the inner command buffer yet need to be sent. 524 unsafe { 525 self.inner.pipeline_barrier(&self.pending_barrier); 526 let start = self.first_unflushed; 527 self.barriers.push(start); // Track inserted barriers 528 for command in &mut self.commands[start..] { 529 command.send(&mut self.inner); 530 } 531 } 532 533 // Transition images to their desired final layout. 534 if !self.is_secondary { 535 unsafe { 536 // TODO: this could be optimized by merging the barrier with the barrier above? 537 let mut barrier = UnsafeCommandBufferBuilderPipelineBarrier::new(); 538 539 for (key, state) in self 540 .resources 541 .iter_mut() 542 .filter(|(key, _)| matches!(key, ResourceKey::Image(..))) 543 { 544 let img = self.commands[state.command_ids[0]].image(state.resource_index); 545 let requested_layout = img.final_layout_requirement(); 546 if requested_layout == state.current_layout { 547 continue; 548 } 549 550 barrier.add_image_memory_barrier( 551 img, 552 img.current_miplevels_access(), 553 img.current_layer_levels_access(), 554 state.memory.stages, 555 state.memory.access, 556 PipelineStages { 557 top_of_pipe: true, 558 ..PipelineStages::none() 559 }, 560 AccessFlags::none(), 561 true, 562 None, // TODO: queue transfers? 563 state.current_layout, 564 requested_layout, 565 ); 566 567 state.exclusive_any = true; 568 state.current_layout = requested_layout; 569 } 570 571 self.inner.pipeline_barrier(&barrier); 572 } 573 } 574 575 // Build the final resources states. 576 let final_resources_states: FnvHashMap<_, _> = { 577 self.resources 578 .into_iter() 579 .map(|(resource, state)| { 580 let final_state = ResourceFinalState { 581 command_ids: state.command_ids, 582 resource_index: state.resource_index, 583 final_stages: state.memory.stages, 584 final_access: state.memory.access, 585 exclusive: state.exclusive_any, 586 initial_layout: state.initial_layout, 587 final_layout: state.current_layout, 588 image_uninitialized_safe: state.image_uninitialized_safe, 589 }; 590 (resource, final_state) 591 }) 592 .collect() 593 }; 594 595 Ok(SyncCommandBuffer { 596 inner: self.inner.build()?, 597 buffers: self.buffers, 598 images: self.images, 599 resources: final_resources_states, 600 commands: self.commands, 601 barriers: self.barriers, 602 }) 603 } 604 605 /// Returns the descriptor set currently bound to a given set number, or `None` if nothing has 606 /// been bound yet. bound_descriptor_set( &self, pipeline_bind_point: PipelineBindPoint, set_num: u32, ) -> Option<(&dyn DescriptorSet, &[u32])>607 pub(crate) fn bound_descriptor_set( 608 &self, 609 pipeline_bind_point: PipelineBindPoint, 610 set_num: u32, 611 ) -> Option<(&dyn DescriptorSet, &[u32])> { 612 self.bindings 613 .descriptor_sets 614 .get(&pipeline_bind_point) 615 .and_then(|sets| { 616 sets.get(&set_num) 617 .map(|cmd| cmd.bound_descriptor_set(set_num)) 618 }) 619 } 620 621 /// Returns the index buffer currently bound, or `None` if nothing has been bound yet. bound_index_buffer(&self) -> Option<&dyn BufferAccess>622 pub(crate) fn bound_index_buffer(&self) -> Option<&dyn BufferAccess> { 623 self.bindings 624 .index_buffer 625 .as_ref() 626 .map(|cmd| cmd.bound_index_buffer()) 627 } 628 629 /// Returns the compute pipeline currently bound, or `None` if nothing has been bound yet. bound_pipeline_compute(&self) -> Option<&dyn ComputePipelineAbstract>630 pub(crate) fn bound_pipeline_compute(&self) -> Option<&dyn ComputePipelineAbstract> { 631 self.bindings 632 .pipeline_compute 633 .as_ref() 634 .map(|cmd| cmd.bound_pipeline_compute()) 635 } 636 637 /// Returns the graphics pipeline currently bound, or `None` if nothing has been bound yet. bound_pipeline_graphics(&self) -> Option<&dyn GraphicsPipelineAbstract>638 pub(crate) fn bound_pipeline_graphics(&self) -> Option<&dyn GraphicsPipelineAbstract> { 639 self.bindings 640 .pipeline_graphics 641 .as_ref() 642 .map(|cmd| cmd.bound_pipeline_graphics()) 643 } 644 645 /// Returns the vertex buffer currently bound to a given binding slot number, or `None` if 646 /// nothing has been bound yet. bound_vertex_buffer(&self, binding_num: u32) -> Option<&dyn BufferAccess>647 pub(crate) fn bound_vertex_buffer(&self, binding_num: u32) -> Option<&dyn BufferAccess> { 648 self.bindings 649 .vertex_buffers 650 .get(&binding_num) 651 .map(|cmd| cmd.bound_vertex_buffer(binding_num)) 652 } 653 } 654 655 unsafe impl DeviceOwned for SyncCommandBufferBuilder { 656 #[inline] device(&self) -> &Arc<Device>657 fn device(&self) -> &Arc<Device> { 658 self.inner.device() 659 } 660 } 661 662 impl fmt::Debug for SyncCommandBufferBuilder { 663 #[inline] fmt(&self, f: &mut fmt::Formatter) -> fmt::Result664 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 665 fmt::Debug::fmt(&self.inner, f) 666 } 667 } 668 669 /// Error returned if the builder detects that there's an unsolvable conflict. 670 #[derive(Debug, Clone)] 671 pub enum SyncCommandBufferBuilderError { 672 /// Unsolvable conflict. 673 Conflict { 674 command1_name: &'static str, 675 command1_param: Cow<'static, str>, 676 command1_offset: usize, 677 678 command2_name: &'static str, 679 command2_param: Cow<'static, str>, 680 command2_offset: usize, 681 }, 682 683 ExecError(CommandBufferExecError), 684 } 685 686 impl error::Error for SyncCommandBufferBuilderError {} 687 688 impl fmt::Display for SyncCommandBufferBuilderError { 689 #[inline] fmt(&self, fmt: &mut fmt::Formatter) -> Result<(), fmt::Error>690 fn fmt(&self, fmt: &mut fmt::Formatter) -> Result<(), fmt::Error> { 691 match self { 692 SyncCommandBufferBuilderError::Conflict { .. } => write!(fmt, "unsolvable conflict"), 693 SyncCommandBufferBuilderError::ExecError(err) => err.fmt(fmt), 694 } 695 } 696 } 697 698 impl From<CommandBufferExecError> for SyncCommandBufferBuilderError { 699 #[inline] from(val: CommandBufferExecError) -> Self700 fn from(val: CommandBufferExecError) -> Self { 701 SyncCommandBufferBuilderError::ExecError(val) 702 } 703 } 704 705 /// Type of resource whose state is to be tracked. 706 #[derive(Debug, Copy, Clone, PartialEq, Eq)] 707 enum KeyTy { 708 Buffer, 709 Image, 710 } 711 712 // State of a resource during the building of the command buffer. 713 #[derive(Debug, Clone)] 714 struct ResourceState { 715 // Indices of the commands that contain the resource. 716 command_ids: Vec<usize>, 717 718 // Index of the resource within the first command in `command_ids`. 719 resource_index: usize, 720 721 // Memory access of the command that last used this resource. 722 memory: PipelineMemoryAccess, 723 724 // True if the resource was used in exclusive mode at any point during the building of the 725 // command buffer. Also true if an image layout transition or queue transfer has been performed. 726 exclusive_any: bool, 727 728 // Layout at the first use of the resource by the command buffer. Can be `Undefined` if we 729 // don't care. 730 initial_layout: ImageLayout, 731 732 // Current layout at this stage of the building. 733 current_layout: ImageLayout, 734 735 // Extra context of how the image will be used 736 image_uninitialized_safe: ImageUninitializedSafe, 737 } 738 739 /// Holds the index of the most recent command that binds a particular resource, or `None` if 740 /// nothing has been bound yet. 741 #[derive(Debug, Default)] 742 struct BindingState { 743 descriptor_sets: FnvHashMap<PipelineBindPoint, FnvHashMap<u32, Arc<dyn Command + Send + Sync>>>, 744 index_buffer: Option<Arc<dyn Command + Send + Sync>>, 745 pipeline_compute: Option<Arc<dyn Command + Send + Sync>>, 746 pipeline_graphics: Option<Arc<dyn Command + Send + Sync>>, 747 vertex_buffers: FnvHashMap<u32, Arc<dyn Command + Send + Sync>>, 748 } 749