aboutsummaryrefslogtreecommitdiff
path: root/src/command_buffer/synced/builder.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/command_buffer/synced/builder.rs')
-rw-r--r--src/command_buffer/synced/builder.rs748
1 files changed, 748 insertions, 0 deletions
diff --git a/src/command_buffer/synced/builder.rs b/src/command_buffer/synced/builder.rs
new file mode 100644
index 0000000..541ccbb
--- /dev/null
+++ b/src/command_buffer/synced/builder.rs
@@ -0,0 +1,748 @@
+// Copyright (c) 2016 The vulkano developers
+// Licensed under the Apache License, Version 2.0
+// <LICENSE-APACHE or
+// https://www.apache.org/licenses/LICENSE-2.0> or the MIT
+// license <LICENSE-MIT or https://opensource.org/licenses/MIT>,
+// at your option. All files in the project carrying such
+// notice may not be copied, modified, or distributed except
+// according to those terms.
+
+pub use self::commands::SyncCommandBufferBuilderBindDescriptorSets;
+pub use self::commands::SyncCommandBufferBuilderBindVertexBuffer;
+pub use self::commands::SyncCommandBufferBuilderExecuteCommands;
+use super::Command;
+use super::ResourceFinalState;
+use super::ResourceKey;
+use super::ResourceLocation;
+use super::SyncCommandBuffer;
+use crate::buffer::BufferAccess;
+use crate::command_buffer::pool::UnsafeCommandPoolAlloc;
+use crate::command_buffer::sys::UnsafeCommandBufferBuilder;
+use crate::command_buffer::sys::UnsafeCommandBufferBuilderPipelineBarrier;
+use crate::command_buffer::CommandBufferExecError;
+use crate::command_buffer::CommandBufferLevel;
+use crate::command_buffer::CommandBufferUsage;
+use crate::command_buffer::ImageUninitializedSafe;
+use crate::descriptor_set::DescriptorSet;
+use crate::device::Device;
+use crate::device::DeviceOwned;
+use crate::image::ImageLayout;
+use crate::pipeline::{ComputePipelineAbstract, GraphicsPipelineAbstract, PipelineBindPoint};
+use crate::render_pass::FramebufferAbstract;
+use crate::sync::AccessFlags;
+use crate::sync::PipelineMemoryAccess;
+use crate::sync::PipelineStages;
+use crate::OomError;
+use fnv::FnvHashMap;
+use std::borrow::Cow;
+use std::collections::hash_map::Entry;
+use std::error;
+use std::fmt;
+use std::sync::Arc;
+
+#[path = "commands.rs"]
+mod commands;
+
+/// 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.
+///
+/// Since the implementation needs to cache commands in a `Vec`, most methods have additional
+/// `Send + Sync + 'static` trait requirements on their generics.
+///
+/// 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 {
+ // The actual Vulkan command buffer builder.
+ inner: UnsafeCommandBufferBuilder,
+
+ // Stores all the commands that were added to the sync builder. Some of them are maybe not
+ // submitted to the inner builder yet.
+ // Each command owns the resources it uses (buffers, images, pipelines, descriptor sets etc.),
+ // references to any of these must be indirect in the form of a command index + resource id.
+ commands: Vec<Arc<dyn Command + Send + Sync>>,
+
+ // Prototype for the pipeline barrier that must be submitted before flushing the commands
+ // in `commands`.
+ pending_barrier: UnsafeCommandBufferBuilderPipelineBarrier,
+
+ // Locations within commands that pipeline barriers were inserted. For debugging purposes.
+ // TODO: present only in cfg(debug_assertions)?
+ barriers: Vec<usize>,
+
+ // 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.
+ latest_render_pass_enter: Option<usize>,
+
+ // Stores the current state of buffers and images that are in use by the command buffer.
+ resources: FnvHashMap<ResourceKey, ResourceState>,
+
+ // Resources and their accesses. Used for executing secondary command buffers in a primary.
+ buffers: Vec<(ResourceLocation, PipelineMemoryAccess)>,
+ images: Vec<(
+ ResourceLocation,
+ PipelineMemoryAccess,
+ ImageLayout,
+ ImageLayout,
+ ImageUninitializedSafe,
+ )>,
+
+ // State of bindings.
+ bindings: BindingState,
+
+ // `true` if the builder has been put in an inconsistent state. This happens when
+ // `append_command` throws an error, because some changes to the internal state have already
+ // been made at that point and can't be reverted.
+ // TODO: throw the error in `append_command` _before_ any state changes are made,
+ // so that this is no longer needed.
+ is_poisoned: bool,
+
+ // True if we're a secondary command buffer.
+ is_secondary: bool,
+}
+
+impl SyncCommandBufferBuilder {
+ /// Builds a new `SyncCommandBufferBuilder`. The parameters are the same as the
+ /// `UnsafeCommandBufferBuilder::new` function.
+ ///
+ /// # Safety
+ ///
+ /// See `UnsafeCommandBufferBuilder::new()`.
+ pub unsafe fn new<F>(
+ pool_alloc: &UnsafeCommandPoolAlloc,
+ level: CommandBufferLevel<F>,
+ usage: CommandBufferUsage,
+ ) -> Result<SyncCommandBufferBuilder, OomError>
+ where
+ F: FramebufferAbstract,
+ {
+ let (is_secondary, inside_render_pass) = match level {
+ CommandBufferLevel::Primary => (false, false),
+ CommandBufferLevel::Secondary(ref inheritance) => {
+ (true, inheritance.render_pass.is_some())
+ }
+ };
+
+ let cmd = UnsafeCommandBufferBuilder::new(pool_alloc, level, usage)?;
+ Ok(SyncCommandBufferBuilder::from_unsafe_cmd(
+ cmd,
+ is_secondary,
+ 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(
+ cmd: UnsafeCommandBufferBuilder,
+ is_secondary: bool,
+ inside_render_pass: bool,
+ ) -> SyncCommandBufferBuilder {
+ let latest_render_pass_enter = if inside_render_pass { Some(0) } else { None };
+
+ SyncCommandBufferBuilder {
+ inner: cmd,
+ commands: Vec::new(),
+ pending_barrier: UnsafeCommandBufferBuilderPipelineBarrier::new(),
+ barriers: Vec::new(),
+ first_unflushed: 0,
+ latest_render_pass_enter,
+ resources: FnvHashMap::default(),
+ buffers: Vec::new(),
+ images: Vec::new(),
+ bindings: Default::default(),
+ is_poisoned: false,
+ is_secondary,
+ }
+ }
+
+ // 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.
+ #[inline]
+ fn append_command<C>(
+ &mut self,
+ command: C,
+ resources: &[(
+ KeyTy,
+ Option<(
+ PipelineMemoryAccess,
+ ImageLayout,
+ ImageLayout,
+ ImageUninitializedSafe,
+ )>,
+ )],
+ ) -> Result<(), SyncCommandBufferBuilderError>
+ where
+ C: Command + Send + Sync + 'static,
+ {
+ // TODO: see comment for the `is_poisoned` member in the struct
+ assert!(
+ !self.is_poisoned,
+ "The builder has been put in an inconsistent state by a previous error"
+ );
+
+ // Note that we don't submit the command to the inner command buffer yet.
+ let (latest_command_id, end) = {
+ self.commands.push(Arc::new(command));
+ let latest_command_id = self.commands.len() - 1;
+ let end = self.latest_render_pass_enter.unwrap_or(latest_command_id);
+ (latest_command_id, end)
+ };
+ let mut last_cmd_buffer = 0;
+ let mut last_cmd_image = 0;
+
+ for &(resource_ty, resource) in resources {
+ if let Some((memory, start_layout, end_layout, image_uninitialized_safe)) = resource {
+ // Anti-dumbness checks.
+ debug_assert!(memory.exclusive || start_layout == end_layout);
+ debug_assert!(memory.access.is_compatible_with(&memory.stages));
+ debug_assert!(resource_ty != KeyTy::Image || end_layout != ImageLayout::Undefined);
+ debug_assert!(
+ resource_ty != KeyTy::Buffer || start_layout == ImageLayout::Undefined
+ );
+ debug_assert!(resource_ty != KeyTy::Buffer || end_layout == ImageLayout::Undefined);
+ debug_assert_ne!(end_layout, ImageLayout::Preinitialized);
+
+ let (resource_key, resource_index) = match resource_ty {
+ KeyTy::Buffer => {
+ let buffer = self.commands[latest_command_id].buffer(last_cmd_buffer);
+ (ResourceKey::from(buffer), last_cmd_buffer)
+ }
+ KeyTy::Image => {
+ let image = self.commands[latest_command_id].image(last_cmd_image);
+ (ResourceKey::from(image), last_cmd_image)
+ }
+ };
+
+ match self.resources.entry(resource_key) {
+ // Situation where this resource was used before in this command buffer.
+ Entry::Occupied(mut entry) => {
+ // `collision_cmd_ids` contains the IDs of the commands that we are potentially
+ // colliding with.
+ let collision_cmd_ids = &entry.get().command_ids;
+ debug_assert!(collision_cmd_ids.iter().all(|id| *id <= latest_command_id));
+
+ let entry_key_resource_index = entry.get().resource_index;
+
+ // Find out if we have a collision with the pending commands.
+ if memory.exclusive
+ || entry.get().memory.exclusive
+ || entry.get().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.
+ let first_unflushed_cmd_id = self.first_unflushed;
+
+ if collision_cmd_ids
+ .iter()
+ .any(|command_id| *command_id >= first_unflushed_cmd_id)
+ || entry.get().current_layout != start_layout
+ {
+ unsafe {
+ // Flush the pending barrier.
+ self.inner.pipeline_barrier(&self.pending_barrier);
+ self.pending_barrier =
+ UnsafeCommandBufferBuilderPipelineBarrier::new();
+
+ // Flush the commands if possible, or return an error if not possible.
+ {
+ let start = self.first_unflushed;
+ self.barriers.push(start); // Track inserted barriers
+
+ if let Some(collision_cmd_id) = collision_cmd_ids
+ .iter()
+ .find(|command_id| **command_id >= end)
+ {
+ // TODO: see comment for the `is_poisoned` member in the struct
+ self.is_poisoned = true;
+
+ let cmd1 = &self.commands[*collision_cmd_id];
+ let cmd2 = &self.commands[latest_command_id];
+
+ return Err(SyncCommandBufferBuilderError::Conflict {
+ command1_name: cmd1.name(),
+ command1_param: match resource_ty {
+ KeyTy::Buffer => {
+ cmd1.buffer_name(entry_key_resource_index)
+ }
+ KeyTy::Image => {
+ cmd1.image_name(entry_key_resource_index)
+ }
+ },
+ command1_offset: *collision_cmd_id,
+
+ command2_name: cmd2.name(),
+ command2_param: match resource_ty {
+ KeyTy::Buffer => {
+ cmd2.buffer_name(resource_index)
+ }
+ KeyTy::Image => cmd2.image_name(resource_index),
+ },
+ command2_offset: latest_command_id,
+ });
+ }
+ for command in &mut self.commands[start..end] {
+ command.send(&mut self.inner);
+ }
+ self.first_unflushed = end;
+ }
+ }
+ }
+
+ entry.get_mut().command_ids.push(latest_command_id);
+ let entry = entry.into_mut();
+
+ // Modify the pipeline barrier to handle the collision.
+ unsafe {
+ match resource_ty {
+ KeyTy::Buffer => {
+ let buf =
+ self.commands[latest_command_id].buffer(resource_index);
+
+ let b = &mut self.pending_barrier;
+ b.add_buffer_memory_barrier(
+ buf,
+ entry.memory.stages,
+ entry.memory.access,
+ memory.stages,
+ memory.access,
+ true,
+ None,
+ 0,
+ buf.size(),
+ );
+ }
+
+ KeyTy::Image => {
+ let img =
+ self.commands[latest_command_id].image(resource_index);
+
+ let b = &mut self.pending_barrier;
+ b.add_image_memory_barrier(
+ img,
+ img.current_miplevels_access(),
+ img.current_layer_levels_access(),
+ entry.memory.stages,
+ entry.memory.access,
+ memory.stages,
+ memory.access,
+ true,
+ None,
+ entry.current_layout,
+ start_layout,
+ );
+ }
+ };
+ }
+
+ // Update state.
+ entry.memory = memory;
+ entry.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.
+ entry.current_layout = end_layout;
+ }
+ } else {
+ // There is no collision. Simply merge the stages and accesses.
+ // TODO: what about simplifying the newly-constructed stages/accesses?
+ // this would simplify the job of the driver, but is it worth it?
+ let entry = entry.into_mut();
+ entry.memory.stages |= memory.stages;
+ entry.memory.access |= memory.access;
+ }
+ }
+
+ // Situation where this is the first time we use this resource in this command buffer.
+ Entry::Vacant(entry) => {
+ // We need to perform some tweaks if the initial layout requirement of the image
+ // is different from the first layout usage.
+ let mut actually_exclusive = memory.exclusive;
+ let mut actual_start_layout = start_layout;
+
+ if !self.is_secondary
+ && resource_ty == KeyTy::Image
+ && start_layout != ImageLayout::Undefined
+ && start_layout != ImageLayout::Preinitialized
+ {
+ let img = self.commands[latest_command_id].image(resource_index);
+ let initial_layout_requirement = img.initial_layout_requirement();
+
+ // Checks if the image is initialized and transitions it
+ // if it isn't
+ let is_layout_initialized = img.is_layout_initialized();
+
+ if initial_layout_requirement != start_layout || !is_layout_initialized
+ {
+ // Note that we transition from `bottom_of_pipe`, which means that we
+ // wait for all the previous commands to be entirely finished. This is
+ // suboptimal, but:
+ //
+ // - If we're at the start of the command buffer we have no choice anyway,
+ // because we have no knowledge about what comes before.
+ // - If we're in the middle of the command buffer, this pipeline is going
+ // to be merged with an existing barrier. While it may still be
+ // suboptimal in some cases, in the general situation it will be ok.
+ //
+ unsafe {
+ let from_layout = if is_layout_initialized {
+ actually_exclusive = true;
+ initial_layout_requirement
+ } else {
+ if img.preinitialized_layout() {
+ ImageLayout::Preinitialized
+ } else {
+ ImageLayout::Undefined
+ }
+ };
+ if initial_layout_requirement != start_layout {
+ actual_start_layout = initial_layout_requirement;
+ }
+ let b = &mut self.pending_barrier;
+ b.add_image_memory_barrier(
+ img,
+ img.current_miplevels_access(),
+ img.current_layer_levels_access(),
+ PipelineStages {
+ bottom_of_pipe: true,
+ ..PipelineStages::none()
+ },
+ AccessFlags::none(),
+ memory.stages,
+ memory.access,
+ true,
+ None,
+ from_layout,
+ start_layout,
+ );
+ img.layout_initialized();
+ }
+ }
+ }
+
+ entry.insert(ResourceState {
+ command_ids: vec![latest_command_id],
+ resource_index,
+
+ memory: PipelineMemoryAccess {
+ stages: memory.stages,
+ access: memory.access,
+ exclusive: actually_exclusive,
+ },
+ exclusive_any: actually_exclusive,
+ initial_layout: actual_start_layout,
+ current_layout: end_layout, // TODO: what if we reach the end with Undefined? that's not correct?
+ image_uninitialized_safe,
+ });
+ }
+ }
+
+ // Add the resources to the lists
+ // TODO: Perhaps any barriers for a resource in the secondary command buffer will "protect"
+ // its accesses so the primary needs less strict barriers.
+ // Less barriers is more efficient, so worth investigating!
+ let location = ResourceLocation {
+ command_id: latest_command_id,
+ resource_index,
+ };
+
+ match resource_ty {
+ KeyTy::Buffer => {
+ self.buffers.push((location, memory));
+ last_cmd_buffer += 1;
+ }
+ KeyTy::Image => {
+ self.images.push((
+ location,
+ memory,
+ start_layout,
+ end_layout,
+ image_uninitialized_safe,
+ ));
+ last_cmd_image += 1;
+ }
+ }
+ } else {
+ match resource_ty {
+ KeyTy::Buffer => {
+ last_cmd_buffer += 1;
+ }
+ KeyTy::Image => {
+ last_cmd_image += 1;
+ }
+ }
+ }
+ }
+
+ Ok(())
+ }
+
+ /// Builds the command buffer and turns it into a `SyncCommandBuffer`.
+ #[inline]
+ pub fn build(mut self) -> Result<SyncCommandBuffer, OomError> {
+ // TODO: see comment for the `is_poisoned` member in the struct
+ assert!(
+ !self.is_poisoned,
+ "The builder has been put in an inconsistent state by a previous error"
+ );
+
+ 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);
+ 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.is_secondary {
+ unsafe {
+ // TODO: this could be optimized by merging the barrier with the barrier above?
+ let mut barrier = UnsafeCommandBufferBuilderPipelineBarrier::new();
+
+ for (key, state) in self
+ .resources
+ .iter_mut()
+ .filter(|(key, _)| matches!(key, ResourceKey::Image(..)))
+ {
+ let img = self.commands[state.command_ids[0]].image(state.resource_index);
+ let requested_layout = img.final_layout_requirement();
+ if requested_layout == state.current_layout {
+ continue;
+ }
+
+ barrier.add_image_memory_barrier(
+ img,
+ img.current_miplevels_access(),
+ img.current_layer_levels_access(),
+ state.memory.stages,
+ state.memory.access,
+ PipelineStages {
+ top_of_pipe: true,
+ ..PipelineStages::none()
+ },
+ AccessFlags::none(),
+ true,
+ None, // TODO: queue transfers?
+ state.current_layout,
+ requested_layout,
+ );
+
+ state.exclusive_any = true;
+ state.current_layout = requested_layout;
+ }
+
+ self.inner.pipeline_barrier(&barrier);
+ }
+ }
+
+ // Build the final resources states.
+ let final_resources_states: FnvHashMap<_, _> = {
+ self.resources
+ .into_iter()
+ .map(|(resource, state)| {
+ let final_state = ResourceFinalState {
+ command_ids: state.command_ids,
+ resource_index: state.resource_index,
+ final_stages: state.memory.stages,
+ final_access: state.memory.access,
+ exclusive: state.exclusive_any,
+ initial_layout: state.initial_layout,
+ final_layout: state.current_layout,
+ image_uninitialized_safe: state.image_uninitialized_safe,
+ };
+ (resource, final_state)
+ })
+ .collect()
+ };
+
+ Ok(SyncCommandBuffer {
+ inner: self.inner.build()?,
+ buffers: self.buffers,
+ images: self.images,
+ resources: final_resources_states,
+ commands: self.commands,
+ barriers: self.barriers,
+ })
+ }
+
+ /// Returns the descriptor set currently bound to a given set number, or `None` if nothing has
+ /// been bound yet.
+ pub(crate) fn bound_descriptor_set(
+ &self,
+ pipeline_bind_point: PipelineBindPoint,
+ set_num: u32,
+ ) -> Option<(&dyn DescriptorSet, &[u32])> {
+ self.bindings
+ .descriptor_sets
+ .get(&pipeline_bind_point)
+ .and_then(|sets| {
+ sets.get(&set_num)
+ .map(|cmd| cmd.bound_descriptor_set(set_num))
+ })
+ }
+
+ /// Returns the index buffer currently bound, or `None` if nothing has been bound yet.
+ pub(crate) fn bound_index_buffer(&self) -> Option<&dyn BufferAccess> {
+ self.bindings
+ .index_buffer
+ .as_ref()
+ .map(|cmd| cmd.bound_index_buffer())
+ }
+
+ /// Returns the compute pipeline currently bound, or `None` if nothing has been bound yet.
+ pub(crate) fn bound_pipeline_compute(&self) -> Option<&dyn ComputePipelineAbstract> {
+ self.bindings
+ .pipeline_compute
+ .as_ref()
+ .map(|cmd| cmd.bound_pipeline_compute())
+ }
+
+ /// Returns the graphics pipeline currently bound, or `None` if nothing has been bound yet.
+ pub(crate) fn bound_pipeline_graphics(&self) -> Option<&dyn GraphicsPipelineAbstract> {
+ self.bindings
+ .pipeline_graphics
+ .as_ref()
+ .map(|cmd| cmd.bound_pipeline_graphics())
+ }
+
+ /// Returns the vertex buffer currently bound to a given binding slot number, or `None` if
+ /// nothing has been bound yet.
+ pub(crate) fn bound_vertex_buffer(&self, binding_num: u32) -> Option<&dyn BufferAccess> {
+ self.bindings
+ .vertex_buffers
+ .get(&binding_num)
+ .map(|cmd| cmd.bound_vertex_buffer(binding_num))
+ }
+}
+
+unsafe impl DeviceOwned for SyncCommandBufferBuilder {
+ #[inline]
+ fn device(&self) -> &Arc<Device> {
+ self.inner.device()
+ }
+}
+
+impl fmt::Debug for SyncCommandBufferBuilder {
+ #[inline]
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ fmt::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 {
+ command1_name: &'static str,
+ command1_param: Cow<'static, str>,
+ command1_offset: usize,
+
+ command2_name: &'static str,
+ command2_param: Cow<'static, str>,
+ command2_offset: usize,
+ },
+
+ ExecError(CommandBufferExecError),
+}
+
+impl error::Error for SyncCommandBufferBuilderError {}
+
+impl fmt::Display for SyncCommandBufferBuilderError {
+ #[inline]
+ fn fmt(&self, fmt: &mut fmt::Formatter) -> Result<(), fmt::Error> {
+ match self {
+ SyncCommandBufferBuilderError::Conflict { .. } => write!(fmt, "unsolvable conflict"),
+ SyncCommandBufferBuilderError::ExecError(err) => err.fmt(fmt),
+ }
+ }
+}
+
+impl From<CommandBufferExecError> for SyncCommandBufferBuilderError {
+ #[inline]
+ fn from(val: CommandBufferExecError) -> Self {
+ SyncCommandBufferBuilderError::ExecError(val)
+ }
+}
+
+/// Type of resource whose state is to be tracked.
+#[derive(Debug, Copy, Clone, PartialEq, Eq)]
+enum KeyTy {
+ Buffer,
+ Image,
+}
+
+// State of a resource during the building of the command buffer.
+#[derive(Debug, Clone)]
+struct ResourceState {
+ // Indices of the commands that contain the resource.
+ command_ids: Vec<usize>,
+
+ // Index of the resource within the first command in `command_ids`.
+ resource_index: usize,
+
+ // 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,
+
+ // Layout at the first use of the resource by the command buffer. Can be `Undefined` if we
+ // don't care.
+ initial_layout: ImageLayout,
+
+ // Current layout at this stage of the building.
+ current_layout: ImageLayout,
+
+ // Extra context of how the image will be used
+ image_uninitialized_safe: ImageUninitializedSafe,
+}
+
+/// Holds the index of the most recent command that binds a particular resource, or `None` if
+/// nothing has been bound yet.
+#[derive(Debug, Default)]
+struct BindingState {
+ descriptor_sets: FnvHashMap<PipelineBindPoint, FnvHashMap<u32, Arc<dyn Command + Send + Sync>>>,
+ index_buffer: Option<Arc<dyn Command + Send + Sync>>,
+ pipeline_compute: Option<Arc<dyn Command + Send + Sync>>,
+ pipeline_graphics: Option<Arc<dyn Command + Send + Sync>>,
+ vertex_buffers: FnvHashMap<u32, Arc<dyn Command + Send + Sync>>,
+}