// Copyright (c) 2016 The vulkano developers // Licensed under the Apache License, Version 2.0 // or the MIT // license , // at your option. All files in the project carrying such // notice may not be copied, modified, or distributed except // according to those terms. use super::{Command, Resource, SyncCommandBuffer}; pub use crate::command_buffer::commands::{ bind_push::{ SyncCommandBufferBuilderBindDescriptorSets, SyncCommandBufferBuilderBindVertexBuffer, }, secondary::SyncCommandBufferBuilderExecuteCommands, }; use crate::{ buffer::{Buffer, Subbuffer}, command_buffer::{ pool::CommandPoolAlloc, sys::{CommandBufferBeginInfo, UnsafeCommandBufferBuilder}, CommandBufferBufferRangeUsage, CommandBufferBufferUsage, CommandBufferExecError, CommandBufferImageRangeUsage, CommandBufferImageUsage, CommandBufferLevel, CommandBufferResourcesUsage, ResourceUseRef, SecondaryCommandBufferBufferUsage, SecondaryCommandBufferImageUsage, SecondaryCommandBufferResourcesUsage, }, descriptor_set::{DescriptorSetResources, DescriptorSetWithOffsets}, device::{Device, DeviceOwned}, image::{sys::Image, ImageAccess, ImageAspects, ImageLayout, ImageSubresourceRange}, pipeline::{ graphics::{ color_blend::LogicOp, depth_stencil::{CompareOp, StencilOps}, input_assembly::{IndexType, PrimitiveTopology}, rasterization::{CullMode, DepthBias, FrontFace, LineStipple}, viewport::{Scissor, Viewport}, }, ComputePipeline, DynamicState, GraphicsPipeline, PipelineBindPoint, PipelineLayout, }, range_map::RangeMap, range_set::RangeSet, sync::{ AccessFlags, BufferMemoryBarrier, DependencyInfo, ImageMemoryBarrier, PipelineMemoryAccess, PipelineStages, }, DeviceSize, OomError, VulkanObject, }; use ahash::HashMap; use smallvec::SmallVec; use std::{ collections::hash_map::Entry, error::Error, fmt::{Debug, Display, Error as FmtError, Formatter}, ops::{Range, RangeInclusive}, sync::Arc, }; /// Wrapper around `UnsafeCommandBufferBuilder` that handles synchronization for you. /// /// Each method of the `UnsafeCommandBufferBuilder` has an equivalent in this wrapper, except /// for `pipeline_layout` which is automatically handled. This wrapper automatically builds /// pipeline barriers, keeps used resources alive and implements the `CommandBuffer` trait. /// /// If this builder finds out that a command isn't valid because of synchronization reasons (eg. /// trying to copy from a buffer to an image which share the same memory), then an error is /// returned. /// Note that all methods are still unsafe, because this builder doesn't check the validity of /// the commands except for synchronization purposes. The builder may panic if you pass invalid /// commands. pub struct SyncCommandBufferBuilder { inner: UnsafeCommandBufferBuilder, level: CommandBufferLevel, // Stores all the commands that were added to the sync builder. Some of them are maybe not // submitted to the inner builder yet. pub(in crate::command_buffer) commands: Vec>, // Prototype for the pipeline barrier that must be submitted before flushing the commands // in `commands`. pending_barrier: DependencyInfo, // Locations within commands that pipeline barriers were inserted. For debugging purposes. // TODO: present only in cfg(debug_assertions)? barriers: Vec, // Only the commands before `first_unflushed` have already been sent to the inner // `UnsafeCommandBufferBuilder`. first_unflushed: usize, // If we're currently inside a render pass, contains the index of the `CmdBeginRenderPass` // command. pub(in crate::command_buffer) latest_render_pass_enter: Option, // Stores the current state of buffers and images that are in use by the command buffer. buffers2: HashMap, RangeMap>, images2: HashMap, RangeMap>, // Resources and their accesses. Used for executing secondary command buffers in a primary. secondary_resources_usage: SecondaryCommandBufferResourcesUsage, // Current binding/setting state. pub(in crate::command_buffer) current_state: CurrentState, } impl SyncCommandBufferBuilder { /// Builds a new `SyncCommandBufferBuilder`. The parameters are the same as the /// `UnsafeCommandBufferBuilder::new` function. /// /// # Safety /// /// See `UnsafeCommandBufferBuilder::new()`. #[inline] pub unsafe fn new( pool_alloc: &CommandPoolAlloc, begin_info: CommandBufferBeginInfo, ) -> Result { let level = pool_alloc.level(); let inside_render_pass = level == CommandBufferLevel::Secondary && begin_info .inheritance_info .as_ref() .unwrap() .render_pass .is_some(); let inner = UnsafeCommandBufferBuilder::new(pool_alloc, begin_info)?; Ok(SyncCommandBufferBuilder::from_unsafe_cmd( inner, level, inside_render_pass, )) } /// Builds a `SyncCommandBufferBuilder` from an existing `UnsafeCommandBufferBuilder`. /// /// # Safety /// /// See `UnsafeCommandBufferBuilder::new()`. /// /// In addition to this, the `UnsafeCommandBufferBuilder` should be empty. If it isn't, then /// you must take into account the fact that the `SyncCommandBufferBuilder` won't be aware of /// any existing resource usage. #[inline] pub unsafe fn from_unsafe_cmd( inner: UnsafeCommandBufferBuilder, level: CommandBufferLevel, inside_render_pass: bool, ) -> SyncCommandBufferBuilder { let latest_render_pass_enter = if inside_render_pass { Some(0) } else { None }; SyncCommandBufferBuilder { inner, level, commands: Vec::new(), pending_barrier: DependencyInfo::default(), barriers: Vec::new(), first_unflushed: 0, latest_render_pass_enter, buffers2: HashMap::default(), images2: HashMap::default(), secondary_resources_usage: Default::default(), current_state: Default::default(), } } /// Returns the binding/setting state. #[inline] pub fn state(&self) -> CommandBufferBuilderState<'_> { CommandBufferBuilderState { current_state: &self.current_state, } } /// Resets the binding/setting state. /// /// This must be called after any command that changes the state in an undefined way, e.g. /// executing a secondary command buffer. #[inline] pub fn reset_state(&mut self) { self.current_state = Default::default(); } pub(in crate::command_buffer) fn check_resource_conflicts( &self, resource: &(ResourceUseRef, Resource), ) -> Result<(), SyncCommandBufferBuilderError> { let &(current_use_ref, ref resource) = resource; match *resource { Resource::Buffer { ref buffer, ref range, ref memory, } => { debug_assert!(AccessFlags::from(memory.stages).contains(memory.access)); if let Some(previous_use_ref) = self.find_buffer_conflict(buffer, range.clone(), memory) { return Err(SyncCommandBufferBuilderError::Conflict { current_use_ref, previous_use_ref, }); } } Resource::Image { ref image, ref subresource_range, ref memory, start_layout, end_layout, } => { debug_assert!(memory.exclusive || start_layout == end_layout); debug_assert!(AccessFlags::from(memory.stages).contains(memory.access)); debug_assert!(end_layout != ImageLayout::Undefined); debug_assert!(end_layout != ImageLayout::Preinitialized); if let Some(previous_use) = self.find_image_conflict( image, subresource_range.clone(), memory, start_layout, end_layout, ) { return Err(SyncCommandBufferBuilderError::Conflict { current_use_ref, previous_use_ref: previous_use, }); } } } Ok(()) } fn find_buffer_conflict( &self, buffer: &Subbuffer<[u8]>, mut range: Range, memory: &PipelineMemoryAccess, ) -> Option { // Barriers work differently in render passes, so if we're in one, we can only insert a // barrier before the start of the render pass. let last_allowed_barrier_index = self.latest_render_pass_enter.unwrap_or(self.commands.len()); range.start += buffer.offset(); range.end += buffer.offset(); let range_map = self.buffers2.get(buffer.buffer())?; for (_range, state) in range_map .range(&range) .filter(|(_range, state)| !state.resource_uses.is_empty()) { debug_assert!(state .resource_uses .iter() .all(|resource_use| resource_use.command_index <= self.commands.len())); if memory.exclusive || state.memory.exclusive { // If there is a resource use at a position beyond where we can insert a // barrier, then there is an unsolvable conflict. if let Some(&use_ref) = state .resource_uses .iter() .find(|resource_use| resource_use.command_index >= last_allowed_barrier_index) { return Some(use_ref); } } } None } fn find_image_conflict( &self, image: &dyn ImageAccess, mut subresource_range: ImageSubresourceRange, memory: &PipelineMemoryAccess, start_layout: ImageLayout, _end_layout: ImageLayout, ) -> Option { // Barriers work differently in render passes, so if we're in one, we can only insert a // barrier before the start of the render pass. let last_allowed_barrier_index = self.latest_render_pass_enter.unwrap_or(self.commands.len()); let inner = image.inner(); subresource_range.array_layers.start += inner.first_layer; subresource_range.array_layers.end += inner.first_layer; subresource_range.mip_levels.start += inner.first_mipmap_level; subresource_range.mip_levels.end += inner.first_mipmap_level; let range_map = self.images2.get(inner.image)?; for range in inner.image.iter_ranges(subresource_range) { for (_range, state) in range_map .range(&range) .filter(|(_range, state)| !state.resource_uses.is_empty()) { debug_assert!(state .resource_uses .iter() .all(|resource_use| resource_use.command_index <= self.commands.len())); // If the command expects the image to be undefined, then we can't // transition it, so use the current layout for both old and new layout. let start_layout = if start_layout == ImageLayout::Undefined { state.current_layout } else { start_layout }; if memory.exclusive || state.memory.exclusive || state.current_layout != start_layout { // If there is a resource use at a position beyond where we can insert a // barrier, then there is an unsolvable conflict. if let Some(&use_ref) = state.resource_uses.iter().find(|resource_use| { resource_use.command_index >= last_allowed_barrier_index }) { return Some(use_ref); } } } } None } /// Adds a command to be processed by the builder. /// /// The `resources` argument should contain each buffer or image used by the command. /// The function will take care of handling the pipeline barrier or flushing. /// /// - The index of the resource within the `resources` slice maps to the resource accessed /// through `Command::buffer(..)` or `Command::image(..)`. /// - `PipelineMemoryAccess` must match the way the resource has been used. /// - `start_layout` and `end_layout` designate the image layout that the image is expected to /// be in when the command starts, and the image layout that the image will be transitioned to /// during the command. When it comes to buffers, you should pass `Undefined` for both. pub(in crate::command_buffer) fn add_resource(&mut self, resource: (ResourceUseRef, Resource)) { let (use_ref, resource) = resource; match resource { Resource::Buffer { buffer, range, memory, } => { self.add_buffer(use_ref, buffer, range, memory); } Resource::Image { image, subresource_range, memory, start_layout, end_layout, } => { self.add_image( use_ref, image, subresource_range, memory, start_layout, end_layout, ); } } } fn add_buffer( &mut self, use_ref: ResourceUseRef, buffer: Subbuffer<[u8]>, mut range: Range, memory: PipelineMemoryAccess, ) { self.secondary_resources_usage .buffers .push(SecondaryCommandBufferBufferUsage { use_ref, buffer: buffer.clone(), range: range.clone(), memory, }); // Barriers work differently in render passes, so if we're in one, we can only insert a // barrier before the start of the render pass. let last_allowed_barrier_index = self .latest_render_pass_enter .unwrap_or(self.commands.len() - 1); range.start += buffer.offset(); range.end += buffer.offset(); let range_map = self .buffers2 .entry(buffer.buffer().clone()) .or_insert_with(|| { [( 0..buffer.buffer().size(), BufferState { resource_uses: Vec::new(), memory: PipelineMemoryAccess::default(), exclusive_any: false, }, )] .into_iter() .collect() }); range_map.split_at(&range.start); range_map.split_at(&range.end); for (range, state) in range_map.range_mut(&range) { if state.resource_uses.is_empty() { // This is the first time we use this resource range in this command buffer. state.resource_uses.push(use_ref); state.memory = PipelineMemoryAccess { stages: memory.stages, access: memory.access, exclusive: memory.exclusive, }; state.exclusive_any = memory.exclusive; match self.level { CommandBufferLevel::Primary => { // To be safe, we insert a barrier for all stages and accesses before // the first use, so that there are no hazards with any command buffer // that was previously submitted to the same queue. // This is rather overkill, but since command buffers don't know what // will come before them, it's the only thing that works for now. // TODO: come up with something better let barrier = BufferMemoryBarrier { src_stages: PipelineStages::ALL_COMMANDS, src_access: AccessFlags::MEMORY_READ | AccessFlags::MEMORY_WRITE, dst_stages: PipelineStages::ALL_COMMANDS, dst_access: AccessFlags::MEMORY_READ | AccessFlags::MEMORY_WRITE, range: range.clone(), ..BufferMemoryBarrier::buffer(buffer.buffer().clone()) }; self.pending_barrier.buffer_memory_barriers.push(barrier); } CommandBufferLevel::Secondary => (), } } else { // This resource range was used before in this command buffer. // Find out if we have a collision with the pending commands. if memory.exclusive || state.memory.exclusive { // Collision found between `latest_command_id` and `collision_cmd_id`. // We now want to modify the current pipeline barrier in order to handle the // collision. But since the pipeline barrier is going to be submitted before // the flushed commands, it would be a mistake if `collision_cmd_id` hasn't // been flushed yet. if state .resource_uses .iter() .any(|resource_use| resource_use.command_index >= self.first_unflushed) { unsafe { // Flush the pending barrier. self.inner.pipeline_barrier(&self.pending_barrier); self.pending_barrier.clear(); self.barriers.push(self.first_unflushed); // Track inserted barriers for command in &mut self.commands[self.first_unflushed..last_allowed_barrier_index] { command.send(&mut self.inner); } self.first_unflushed = last_allowed_barrier_index; } } // Modify the pipeline barrier to handle the collision. self.pending_barrier .buffer_memory_barriers .push(BufferMemoryBarrier { src_stages: state.memory.stages, src_access: state.memory.access, dst_stages: memory.stages, dst_access: memory.access, range: range.clone(), ..BufferMemoryBarrier::buffer(buffer.buffer().clone()) }); // Update state. state.memory = memory; state.exclusive_any = true; } else { // There is no collision. Simply merge the stages and accesses. state.memory.stages |= memory.stages; state.memory.access |= memory.access; } state.resource_uses.push(use_ref); } } } fn add_image( &mut self, use_ref: ResourceUseRef, image: Arc, mut subresource_range: ImageSubresourceRange, memory: PipelineMemoryAccess, start_layout: ImageLayout, end_layout: ImageLayout, ) { self.secondary_resources_usage .images .push(SecondaryCommandBufferImageUsage { use_ref, image: image.clone(), subresource_range: subresource_range.clone(), memory, start_layout, end_layout, }); // Barriers work differently in render passes, so if we're in one, we can only insert a // barrier before the start of the render pass. let last_allowed_barrier_index = self .latest_render_pass_enter .unwrap_or(self.commands.len() - 1); let inner = image.inner(); subresource_range.array_layers.start += inner.first_layer; subresource_range.array_layers.end += inner.first_layer; subresource_range.mip_levels.start += inner.first_mipmap_level; subresource_range.mip_levels.end += inner.first_mipmap_level; // VUID-VkImageMemoryBarrier2-image-03320 if !self .device() .enabled_features() .separate_depth_stencil_layouts && image .format() .aspects() .contains(ImageAspects::DEPTH | ImageAspects::STENCIL) { subresource_range.aspects = ImageAspects::DEPTH | ImageAspects::STENCIL; } let range_map = self.images2.entry(inner.image.clone()).or_insert_with(|| { [( 0..inner.image.range_size(), match self.level { CommandBufferLevel::Primary => { // In a primary command buffer, the initial layout is determined // by the image. let initial_layout = if !image.is_layout_initialized() { unsafe { image.layout_initialized(); } image.initial_layout() } else { image.initial_layout_requirement() }; ImageState { resource_uses: Vec::new(), memory: PipelineMemoryAccess::default(), exclusive_any: false, initial_layout, current_layout: initial_layout, final_layout: image.final_layout_requirement(), } } CommandBufferLevel::Secondary => { // In a secondary command buffer, the initial layout is the layout // of the first use. ImageState { resource_uses: Vec::new(), memory: PipelineMemoryAccess::default(), exclusive_any: false, initial_layout: ImageLayout::Undefined, current_layout: ImageLayout::Undefined, final_layout: ImageLayout::Undefined, } } }, )] .into_iter() .collect() }); for range in inner.image.iter_ranges(subresource_range) { range_map.split_at(&range.start); range_map.split_at(&range.end); for (range, state) in range_map.range_mut(&range) { if state.resource_uses.is_empty() { // This is the first time we use this resource range in this command buffer. debug_assert_eq!(state.initial_layout, state.current_layout); state.resource_uses.push(use_ref); state.memory = PipelineMemoryAccess { stages: memory.stages, access: memory.access, exclusive: memory.exclusive, }; state.exclusive_any = memory.exclusive; state.current_layout = end_layout; match self.level { CommandBufferLevel::Primary => { // To be safe, we insert a barrier for all stages and accesses before // the first use, so that there are no hazards with any command buffer // that was previously submitted to the same queue. // This is rather overkill, but since command buffers don't know what // will come before them, it's the only thing that works for now. // TODO: come up with something better let mut barrier = ImageMemoryBarrier { src_stages: PipelineStages::ALL_COMMANDS, src_access: AccessFlags::MEMORY_READ | AccessFlags::MEMORY_WRITE, dst_stages: PipelineStages::ALL_COMMANDS, dst_access: AccessFlags::MEMORY_READ | AccessFlags::MEMORY_WRITE, old_layout: state.initial_layout, new_layout: start_layout, subresource_range: inner.image.range_to_subresources(range.clone()), ..ImageMemoryBarrier::image(inner.image.clone()) }; // If the `new_layout` is Undefined or Preinitialized, this requires // special handling. With the synchronization2 feature enabled, these // values are permitted, as long as `new_layout` equals `old_layout`. // Without synchronization2, these values are never permitted. match barrier.new_layout { ImageLayout::Undefined => { // If the command expects Undefined, that really means it // doesn't care about the layout at all and anything is valid. // We try to keep the old layout, or do a "dummy" transition to // the image's final layout if that isn't possible. barrier.new_layout = if !self.inner.device.enabled_features().synchronization2 && matches!( barrier.old_layout, ImageLayout::Undefined | ImageLayout::Preinitialized ) { image.final_layout_requirement() } else { barrier.old_layout }; } ImageLayout::Preinitialized => { // TODO: put this in find_image_conflict instead? // The image must be in the Preinitialized layout already, we // can't transition it. if state.initial_layout != ImageLayout::Preinitialized { panic!( "The command requires the `Preinitialized layout`, but the initial layout of the image is not `Preinitialized`" ); } // The image is in the Preinitialized layout, but we can't keep // that layout because of the limitations of // pre-synchronization2 barriers. So an error is all we can do. if !self.inner.device.enabled_features().synchronization2 { panic!( "The command requires the `Preinitialized` layout, \ but this is not allowed in pipeline barriers without the `synchronization2` feature enabled" ); } } _ => (), } // A layout transition is a write, so if we perform one, we // need exclusive access. if barrier.old_layout != barrier.new_layout { state.exclusive_any = true; } self.pending_barrier.image_memory_barriers.push(barrier); } CommandBufferLevel::Secondary => { state.initial_layout = start_layout; } } } else { // This resource range was used before in this command buffer. // If the command expects the image to be undefined, then we can't // transition it, so use the current layout for both old and new layout. let start_layout = if start_layout == ImageLayout::Undefined { state.current_layout } else { start_layout }; // Find out if we have a collision with the pending commands. if memory.exclusive || state.memory.exclusive || state.current_layout != start_layout { // Collision found between `latest_command_id` and `collision_cmd_id`. // We now want to modify the current pipeline barrier in order to handle the // collision. But since the pipeline barrier is going to be submitted before // the flushed commands, it would be a mistake if `collision_cmd_id` hasn't // been flushed yet. if state .resource_uses .iter() .any(|resource_use| resource_use.command_index >= self.first_unflushed) || state.current_layout != start_layout { unsafe { // Flush the pending barrier. self.inner.pipeline_barrier(&self.pending_barrier); self.pending_barrier.clear(); self.barriers.push(self.first_unflushed); // Track inserted barriers for command in &mut self.commands [self.first_unflushed..last_allowed_barrier_index] { command.send(&mut self.inner); } self.first_unflushed = last_allowed_barrier_index; } } // Modify the pipeline barrier to handle the collision. self.pending_barrier .image_memory_barriers .push(ImageMemoryBarrier { src_stages: state.memory.stages, src_access: state.memory.access, dst_stages: memory.stages, dst_access: memory.access, old_layout: state.current_layout, new_layout: start_layout, subresource_range: inner.image.range_to_subresources(range.clone()), ..ImageMemoryBarrier::image(inner.image.clone()) }); // Update state. state.memory = memory; state.exclusive_any = true; if memory.exclusive || end_layout != ImageLayout::Undefined { // Only modify the layout in case of a write, because buffer operations // pass `Undefined` for the layout. While a buffer write *must* set the // layout to `Undefined`, a buffer read must not touch it. state.current_layout = end_layout; } } else { // There is no collision. Simply merge the stages and accesses. state.memory.stages |= memory.stages; state.memory.access |= memory.access; } state.resource_uses.push(use_ref); } } } } /// Builds the command buffer and turns it into a `SyncCommandBuffer`. #[inline] pub fn build(mut self) -> Result { debug_assert!(self.latest_render_pass_enter.is_none() || self.pending_barrier.is_empty()); // The commands that haven't been sent to the inner command buffer yet need to be sent. unsafe { self.inner.pipeline_barrier(&self.pending_barrier); self.pending_barrier.clear(); let start = self.first_unflushed; self.barriers.push(start); // Track inserted barriers for command in &mut self.commands[start..] { command.send(&mut self.inner); } } // Transition images to their desired final layout. if self.level == CommandBufferLevel::Primary { unsafe { for (image, range_map) in self.images2.iter_mut() { for (range, state) in range_map .iter_mut() .filter(|(_range, state)| state.final_layout != state.current_layout) { self.pending_barrier .image_memory_barriers .push(ImageMemoryBarrier { src_stages: state.memory.stages, src_access: state.memory.access, dst_stages: PipelineStages::TOP_OF_PIPE, dst_access: AccessFlags::empty(), old_layout: state.current_layout, new_layout: state.final_layout, subresource_range: image.range_to_subresources(range.clone()), ..ImageMemoryBarrier::image(image.clone()) }); state.exclusive_any = true; } } self.inner.pipeline_barrier(&self.pending_barrier); } } let mut resource_usage = CommandBufferResourcesUsage { buffers: self .buffers2 .into_iter() .map(|(buffer, ranges)| CommandBufferBufferUsage { buffer, ranges: ranges .into_iter() .filter(|(_range, state)| !state.resource_uses.is_empty()) .map(|(range, state)| { let first_use = state.resource_uses.into_iter().next(); ( range, CommandBufferBufferRangeUsage { first_use, mutable: state.exclusive_any, }, ) }) .collect(), }) .collect(), images: self .images2 .into_iter() .map(|(image, ranges)| CommandBufferImageUsage { image, ranges: ranges .into_iter() .filter(|(_range, state)| { !state.resource_uses.is_empty() || (self.level == CommandBufferLevel::Primary && state.current_layout != state.final_layout) }) .map(|(range, mut state)| { if self.level == CommandBufferLevel::Primary { state.current_layout = state.final_layout; } let first_use = state.resource_uses.into_iter().next(); ( range, CommandBufferImageRangeUsage { first_use, mutable: state.exclusive_any, expected_layout: state.initial_layout, final_layout: state.current_layout, }, ) }) .collect(), }) .collect(), buffer_indices: Default::default(), image_indices: Default::default(), }; resource_usage.buffer_indices = resource_usage .buffers .iter() .enumerate() .map(|(index, usage)| (usage.buffer.clone(), index)) .collect(); resource_usage.image_indices = resource_usage .images .iter() .enumerate() .map(|(index, usage)| (usage.image.clone(), index)) .collect(); Ok(SyncCommandBuffer { inner: self.inner.build()?, resources_usage: resource_usage, secondary_resources_usage: self.secondary_resources_usage, _commands: self.commands, _barriers: self.barriers, }) } } unsafe impl DeviceOwned for SyncCommandBufferBuilder { #[inline] fn device(&self) -> &Arc { self.inner.device() } } impl Debug for SyncCommandBufferBuilder { fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), FmtError> { Debug::fmt(&self.inner, f) } } /// Error returned if the builder detects that there's an unsolvable conflict. #[derive(Debug, Clone)] pub enum SyncCommandBufferBuilderError { /// Unsolvable conflict. Conflict { current_use_ref: ResourceUseRef, previous_use_ref: ResourceUseRef, }, ExecError(CommandBufferExecError), } impl Error for SyncCommandBufferBuilderError {} impl Display for SyncCommandBufferBuilderError { fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), FmtError> { match self { SyncCommandBufferBuilderError::Conflict { .. } => write!(f, "unsolvable conflict"), SyncCommandBufferBuilderError::ExecError(err) => Display::fmt(err, f), } } } impl From for SyncCommandBufferBuilderError { fn from(val: CommandBufferExecError) -> Self { SyncCommandBufferBuilderError::ExecError(val) } } // State of a resource during the building of the command buffer. #[derive(Clone, PartialEq, Eq)] struct BufferState { // Lists every use of the resource. resource_uses: Vec, // Memory access of the command that last used this resource. memory: PipelineMemoryAccess, // True if the resource was used in exclusive mode at any point during the building of the // command buffer. Also true if an image layout transition or queue transfer has been performed. exclusive_any: bool, } // State of a resource during the building of the command buffer. #[derive(Clone, PartialEq, Eq)] struct ImageState { // Lists every use of the resource. resource_uses: Vec, // Memory access of the command that last used this resource. memory: PipelineMemoryAccess, // True if the resource was used in exclusive mode at any point during the building of the // command buffer. Also true if an image layout transition or queue transfer has been performed. exclusive_any: bool, // The layout that the image range must have when this command buffer is executed. // Can be `Undefined` if we don't care. initial_layout: ImageLayout, // Current layout at this stage of the building. current_layout: ImageLayout, // The layout that the image range will have at the end of the command buffer. // This is only used for primary command buffers. final_layout: ImageLayout, } /// Holds the current binding and setting state. #[derive(Default)] pub(in crate::command_buffer) struct CurrentState { pub(in crate::command_buffer) descriptor_sets: HashMap, pub(in crate::command_buffer) index_buffer: Option<(Subbuffer<[u8]>, IndexType)>, pub(in crate::command_buffer) pipeline_compute: Option>, pub(in crate::command_buffer) pipeline_graphics: Option>, pub(in crate::command_buffer) vertex_buffers: HashMap>, pub(in crate::command_buffer) push_constants: RangeSet, pub(in crate::command_buffer) push_constants_pipeline_layout: Option>, pub(in crate::command_buffer) blend_constants: Option<[f32; 4]>, pub(in crate::command_buffer) color_write_enable: Option>, pub(in crate::command_buffer) cull_mode: Option, pub(in crate::command_buffer) depth_bias: Option, pub(in crate::command_buffer) depth_bias_enable: Option, pub(in crate::command_buffer) depth_bounds: Option>, pub(in crate::command_buffer) depth_bounds_test_enable: Option, pub(in crate::command_buffer) depth_compare_op: Option, pub(in crate::command_buffer) depth_test_enable: Option, pub(in crate::command_buffer) depth_write_enable: Option, pub(in crate::command_buffer) discard_rectangle: HashMap, pub(in crate::command_buffer) front_face: Option, pub(in crate::command_buffer) line_stipple: Option, pub(in crate::command_buffer) line_width: Option, pub(in crate::command_buffer) logic_op: Option, pub(in crate::command_buffer) patch_control_points: Option, pub(in crate::command_buffer) primitive_restart_enable: Option, pub(in crate::command_buffer) primitive_topology: Option, pub(in crate::command_buffer) rasterizer_discard_enable: Option, pub(in crate::command_buffer) scissor: HashMap, pub(in crate::command_buffer) scissor_with_count: Option>, pub(in crate::command_buffer) stencil_compare_mask: StencilStateDynamic, pub(in crate::command_buffer) stencil_op: StencilOpStateDynamic, pub(in crate::command_buffer) stencil_reference: StencilStateDynamic, pub(in crate::command_buffer) stencil_test_enable: Option, pub(in crate::command_buffer) stencil_write_mask: StencilStateDynamic, pub(in crate::command_buffer) viewport: HashMap, pub(in crate::command_buffer) viewport_with_count: Option>, } impl CurrentState { pub(in crate::command_buffer) fn reset_dynamic_states( &mut self, states: impl IntoIterator, ) { for state in states { match state { DynamicState::BlendConstants => self.blend_constants = None, DynamicState::ColorWriteEnable => self.color_write_enable = None, DynamicState::CullMode => self.cull_mode = None, DynamicState::DepthBias => self.depth_bias = None, DynamicState::DepthBiasEnable => self.depth_bias_enable = None, DynamicState::DepthBounds => self.depth_bounds = None, DynamicState::DepthBoundsTestEnable => self.depth_bounds_test_enable = None, DynamicState::DepthCompareOp => self.depth_compare_op = None, DynamicState::DepthTestEnable => self.depth_test_enable = None, DynamicState::DepthWriteEnable => self.depth_write_enable = None, DynamicState::DiscardRectangle => self.discard_rectangle.clear(), DynamicState::ExclusiveScissor => (), // TODO; DynamicState::FragmentShadingRate => (), // TODO: DynamicState::FrontFace => self.front_face = None, DynamicState::LineStipple => self.line_stipple = None, DynamicState::LineWidth => self.line_width = None, DynamicState::LogicOp => self.logic_op = None, DynamicState::PatchControlPoints => self.patch_control_points = None, DynamicState::PrimitiveRestartEnable => self.primitive_restart_enable = None, DynamicState::PrimitiveTopology => self.primitive_topology = None, DynamicState::RasterizerDiscardEnable => self.rasterizer_discard_enable = None, DynamicState::RayTracingPipelineStackSize => (), // TODO: DynamicState::SampleLocations => (), // TODO: DynamicState::Scissor => self.scissor.clear(), DynamicState::ScissorWithCount => self.scissor_with_count = None, DynamicState::StencilCompareMask => self.stencil_compare_mask = Default::default(), DynamicState::StencilOp => self.stencil_op = Default::default(), DynamicState::StencilReference => self.stencil_reference = Default::default(), DynamicState::StencilTestEnable => self.stencil_test_enable = None, DynamicState::StencilWriteMask => self.stencil_write_mask = Default::default(), DynamicState::VertexInput => (), // TODO: DynamicState::VertexInputBindingStride => (), // TODO: DynamicState::Viewport => self.viewport.clear(), DynamicState::ViewportCoarseSampleOrder => (), // TODO: DynamicState::ViewportShadingRatePalette => (), // TODO: DynamicState::ViewportWScaling => (), // TODO: DynamicState::ViewportWithCount => self.viewport_with_count = None, DynamicState::TessellationDomainOrigin => (), // TODO: DynamicState::DepthClampEnable => (), // TODO: DynamicState::PolygonMode => (), // TODO: DynamicState::RasterizationSamples => (), // TODO: DynamicState::SampleMask => (), // TODO: DynamicState::AlphaToCoverageEnable => (), // TODO: DynamicState::AlphaToOneEnable => (), // TODO: DynamicState::LogicOpEnable => (), // TODO: DynamicState::ColorBlendEnable => (), // TODO: DynamicState::ColorBlendEquation => (), // TODO: DynamicState::ColorWriteMask => (), // TODO: DynamicState::RasterizationStream => (), // TODO: DynamicState::ConservativeRasterizationMode => (), // TODO: DynamicState::ExtraPrimitiveOverestimationSize => (), // TODO: DynamicState::DepthClipEnable => (), // TODO: DynamicState::SampleLocationsEnable => (), // TODO: DynamicState::ColorBlendAdvanced => (), // TODO: DynamicState::ProvokingVertexMode => (), // TODO: DynamicState::LineRasterizationMode => (), // TODO: DynamicState::LineStippleEnable => (), // TODO: DynamicState::DepthClipNegativeOneToOne => (), // TODO: DynamicState::ViewportWScalingEnable => (), // TODO: DynamicState::ViewportSwizzle => (), // TODO: DynamicState::CoverageToColorEnable => (), // TODO: DynamicState::CoverageToColorLocation => (), // TODO: DynamicState::CoverageModulationMode => (), // TODO: DynamicState::CoverageModulationTableEnable => (), // TODO: DynamicState::CoverageModulationTable => (), // TODO: DynamicState::ShadingRateImageEnable => (), // TODO: DynamicState::RepresentativeFragmentTestEnable => (), // TODO: DynamicState::CoverageReductionMode => (), // TODO: } } } pub(in crate::command_buffer) fn invalidate_descriptor_sets( &mut self, pipeline_bind_point: PipelineBindPoint, pipeline_layout: Arc, first_set: u32, num_descriptor_sets: u32, ) -> &mut DescriptorSetState { match self.descriptor_sets.entry(pipeline_bind_point) { Entry::Vacant(entry) => entry.insert(DescriptorSetState { descriptor_sets: Default::default(), pipeline_layout, }), Entry::Occupied(entry) => { let state = entry.into_mut(); let invalidate_from = if state.pipeline_layout.handle() == pipeline_layout.handle() { // If we're still using the exact same layout, then of course it's compatible. None } else if state.pipeline_layout.push_constant_ranges() != pipeline_layout.push_constant_ranges() { // If the push constant ranges don't match, // all bound descriptor sets are disturbed. Some(0) } else { // Find the first descriptor set layout in the current pipeline layout that // isn't compatible with the corresponding set in the new pipeline layout. // If an incompatible set was found, all bound sets from that slot onwards will // be disturbed. let current_layouts = state.pipeline_layout.set_layouts(); let new_layouts = pipeline_layout.set_layouts(); let max = (current_layouts.len() as u32).min(first_set + num_descriptor_sets); (0..max).find(|&num| { let num = num as usize; !current_layouts[num].is_compatible_with(&new_layouts[num]) }) }; if let Some(invalidate_from) = invalidate_from { // Remove disturbed sets and set new pipeline layout. state .descriptor_sets .retain(|&num, _| num < invalidate_from); state.pipeline_layout = pipeline_layout; } else if (first_set + num_descriptor_sets) as usize >= state.pipeline_layout.set_layouts().len() { // New layout is a superset of the old one. state.pipeline_layout = pipeline_layout; } state } } } } pub(in crate::command_buffer) struct DescriptorSetState { pub(in crate::command_buffer) descriptor_sets: HashMap, pub(in crate::command_buffer) pipeline_layout: Arc, } #[derive(Clone)] pub enum SetOrPush { Set(DescriptorSetWithOffsets), Push(DescriptorSetResources), } impl SetOrPush { #[inline] pub fn resources(&self) -> &DescriptorSetResources { match self { Self::Set(set) => set.as_ref().0.resources(), Self::Push(resources) => resources, } } #[inline] pub fn dynamic_offsets(&self) -> &[u32] { match self { Self::Set(set) => set.as_ref().1, Self::Push(_) => &[], } } } /// Allows you to retrieve the current state of a command buffer builder. #[derive(Clone, Copy)] pub struct CommandBufferBuilderState<'a> { current_state: &'a CurrentState, } impl<'a> CommandBufferBuilderState<'a> { /// Returns the descriptor set currently bound to a given set number, or `None` if nothing has /// been bound yet. #[inline] pub fn descriptor_set( &self, pipeline_bind_point: PipelineBindPoint, set_num: u32, ) -> Option<&'a SetOrPush> { self.current_state .descriptor_sets .get(&pipeline_bind_point) .and_then(|state| state.descriptor_sets.get(&set_num)) } /// Returns the pipeline layout that describes all currently bound descriptor sets. /// /// This can be the layout used to perform the last bind operation, but it can also be the /// layout of an earlier bind if it was compatible with more recent binds. #[inline] pub fn descriptor_sets_pipeline_layout( &self, pipeline_bind_point: PipelineBindPoint, ) -> Option<&'a Arc> { self.current_state .descriptor_sets .get(&pipeline_bind_point) .map(|state| &state.pipeline_layout) } /// Returns the index buffer currently bound, or `None` if nothing has been bound yet. #[inline] pub fn index_buffer(&self) -> Option<(&'a Subbuffer<[u8]>, IndexType)> { self.current_state .index_buffer .as_ref() .map(|(b, i)| (b, *i)) } /// Returns the compute pipeline currently bound, or `None` if nothing has been bound yet. #[inline] pub fn pipeline_compute(&self) -> Option<&'a Arc> { self.current_state.pipeline_compute.as_ref() } /// Returns the graphics pipeline currently bound, or `None` if nothing has been bound yet. #[inline] pub fn pipeline_graphics(&self) -> Option<&'a Arc> { self.current_state.pipeline_graphics.as_ref() } /// Returns the vertex buffer currently bound to a given binding slot number, or `None` if /// nothing has been bound yet. #[inline] pub fn vertex_buffer(&self, binding_num: u32) -> Option<&'a Subbuffer<[u8]>> { self.current_state.vertex_buffers.get(&binding_num) } /// Returns a set containing push constant bytes that have been set. #[inline] pub fn push_constants(&self) -> &'a RangeSet { &self.current_state.push_constants } /// Returns the pipeline layout that describes the current push constants. /// /// This is the layout used to perform the last push constant write operation. #[inline] pub fn push_constants_pipeline_layout(&self) -> Option<&'a Arc> { self.current_state.push_constants_pipeline_layout.as_ref() } /// Returns the current blend constants, or `None` if nothing has been set yet. #[inline] pub fn blend_constants(&self) -> Option<[f32; 4]> { self.current_state.blend_constants } /// Returns the current color write enable settings, or `None` if nothing has been set yet. #[inline] pub fn color_write_enable(&self) -> Option<&'a [bool]> { self.current_state .color_write_enable .as_ref() .map(|x| x.as_slice()) } /// Returns the current cull mode, or `None` if nothing has been set yet. #[inline] pub fn cull_mode(&self) -> Option { self.current_state.cull_mode } /// Returns the current depth bias settings, or `None` if nothing has been set yet. #[inline] pub fn depth_bias(&self) -> Option { self.current_state.depth_bias } /// Returns whether depth bias is enabled, or `None` if nothing has been set yet. #[inline] pub fn depth_bias_enable(&self) -> Option { self.current_state.depth_bias_enable } /// Returns the current depth bounds settings, or `None` if nothing has been set yet. #[inline] pub fn depth_bounds(&self) -> Option> { self.current_state.depth_bounds.clone() } /// Returns whether depth bound testing is enabled, or `None` if nothing has been set yet. #[inline] pub fn depth_bounds_test_enable(&self) -> Option { self.current_state.depth_bounds_test_enable } /// Returns the current depth compare op, or `None` if nothing has been set yet. #[inline] pub fn depth_compare_op(&self) -> Option { self.current_state.depth_compare_op } /// Returns whether depth testing is enabled, or `None` if nothing has been set yet. #[inline] pub fn depth_test_enable(&self) -> Option { self.current_state.depth_test_enable } /// Returns whether depth write is enabled, or `None` if nothing has been set yet. #[inline] pub fn depth_write_enable(&self) -> Option { self.current_state.depth_write_enable } /// Returns the current discard rectangles, or `None` if nothing has been set yet. #[inline] pub fn discard_rectangle(&self, num: u32) -> Option<&'a Scissor> { self.current_state.discard_rectangle.get(&num) } /// Returns the current front face, or `None` if nothing has been set yet. #[inline] pub fn front_face(&self) -> Option { self.current_state.front_face } /// Returns the current line stipple settings, or `None` if nothing has been set yet. #[inline] pub fn line_stipple(&self) -> Option { self.current_state.line_stipple } /// Returns the current line width, or `None` if nothing has been set yet. #[inline] pub fn line_width(&self) -> Option { self.current_state.line_width } /// Returns the current logic op, or `None` if nothing has been set yet. #[inline] pub fn logic_op(&self) -> Option { self.current_state.logic_op } /// Returns the current number of patch control points, or `None` if nothing has been set yet. #[inline] pub fn patch_control_points(&self) -> Option { self.current_state.patch_control_points } /// Returns whether primitive restart is enabled, or `None` if nothing has been set yet. #[inline] pub fn primitive_restart_enable(&self) -> Option { self.current_state.primitive_restart_enable } /// Returns the current primitive topology, or `None` if nothing has been set yet. #[inline] pub fn primitive_topology(&self) -> Option { self.current_state.primitive_topology } /// Returns whether rasterizer discard is enabled, or `None` if nothing has been set yet. #[inline] pub fn rasterizer_discard_enable(&self) -> Option { self.current_state.rasterizer_discard_enable } /// Returns the current scissor for a given viewport slot, or `None` if nothing has been set /// yet. #[inline] pub fn scissor(&self, num: u32) -> Option<&'a Scissor> { self.current_state.scissor.get(&num) } /// Returns the current viewport-with-count settings, or `None` if nothing has been set yet. #[inline] pub fn scissor_with_count(&self) -> Option<&'a [Scissor]> { self.current_state .scissor_with_count .as_ref() .map(|x| x.as_slice()) } /// Returns the current stencil compare masks. #[inline] pub fn stencil_compare_mask(&self) -> StencilStateDynamic { self.current_state.stencil_compare_mask } /// Returns the current stencil ops. #[inline] pub fn stencil_op(&self) -> StencilOpStateDynamic { self.current_state.stencil_op } /// Returns the current stencil references. #[inline] pub fn stencil_reference(&self) -> StencilStateDynamic { self.current_state.stencil_reference } /// Returns whether stencil testing is enabled, or `None` if nothing has been set yet. #[inline] pub fn stencil_test_enable(&self) -> Option { self.current_state.stencil_test_enable } /// Returns the current stencil write masks. #[inline] pub fn stencil_write_mask(&self) -> StencilStateDynamic { self.current_state.stencil_write_mask } /// Returns the current viewport for a given viewport slot, or `None` if nothing has been set /// yet. #[inline] pub fn viewport(&self, num: u32) -> Option<&'a Viewport> { self.current_state.viewport.get(&num) } /// Returns the current viewport-with-count settings, or `None` if nothing has been set yet. #[inline] pub fn viewport_with_count(&self) -> Option<&'a [Viewport]> { self.current_state .viewport_with_count .as_ref() .map(|x| x.as_slice()) } } /// Holds the current stencil state of a command buffer builder. #[derive(Clone, Copy, Debug, Default)] pub struct StencilStateDynamic { pub front: Option, pub back: Option, } /// Holds the current per-face stencil op state of a command buffer builder. #[derive(Clone, Copy, Debug, Default)] pub struct StencilOpStateDynamic { pub front: Option, pub back: Option, }