aboutsummaryrefslogtreecommitdiff
path: root/src/command_buffer/state_cacher.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/command_buffer/state_cacher.rs')
-rw-r--r--src/command_buffer/state_cacher.rs484
1 files changed, 484 insertions, 0 deletions
diff --git a/src/command_buffer/state_cacher.rs b/src/command_buffer/state_cacher.rs
new file mode 100644
index 0000000..63bfc3d
--- /dev/null
+++ b/src/command_buffer/state_cacher.rs
@@ -0,0 +1,484 @@
+// Copyright (c) 2017 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.
+
+use crate::buffer::BufferAccess;
+use crate::command_buffer::DynamicState;
+use crate::descriptor_set::DescriptorSetWithOffsets;
+use crate::pipeline::input_assembly::IndexType;
+use crate::pipeline::ComputePipelineAbstract;
+use crate::pipeline::GraphicsPipelineAbstract;
+use crate::pipeline::PipelineBindPoint;
+use crate::DeviceSize;
+use crate::VulkanObject;
+use smallvec::SmallVec;
+use std::ops::Range;
+
+/// Keep track of the state of a command buffer builder, so that you don't need to bind objects
+/// that were already bound.
+///
+/// > **Important**: Executing a secondary command buffer invalidates the state of a command buffer
+/// > builder. When you do so, you need to call `invalidate()`.
+pub struct StateCacher {
+ // The dynamic state to synchronize with `CmdSetState`.
+ dynamic_state: DynamicState,
+ // The compute pipeline currently bound. 0 if nothing bound.
+ compute_pipeline: ash::vk::Pipeline,
+ // The graphics pipeline currently bound. 0 if nothing bound.
+ graphics_pipeline: ash::vk::Pipeline,
+ // The descriptor sets for the compute pipeline.
+ compute_descriptor_sets: SmallVec<[(ash::vk::DescriptorSet, SmallVec<[u32; 32]>); 12]>,
+ // The descriptor sets for the graphics pipeline.
+ graphics_descriptor_sets: SmallVec<[(ash::vk::DescriptorSet, SmallVec<[u32; 32]>); 12]>,
+ // If the user starts comparing descriptor sets, but drops the helper struct in the middle of
+ // the processing then we will end up in a weird state. This bool is true when we start
+ // comparing sets, and is set to false when we end up comparing. If it was true when we start
+ // comparing, we know that something bad happened and we flush the cache.
+ poisoned_descriptor_sets: bool,
+ // The vertex buffers currently bound.
+ vertex_buffers: SmallVec<[(ash::vk::Buffer, DeviceSize); 12]>,
+ // Same as `poisoned_descriptor_sets` but for vertex buffers.
+ poisoned_vertex_buffers: bool,
+ // The index buffer, offset, and index type currently bound. `None` if nothing bound.
+ index_buffer: Option<(ash::vk::Buffer, DeviceSize, IndexType)>,
+}
+
+/// Outcome of an operation.
+#[derive(Debug, Copy, Clone, PartialEq, Eq)]
+pub enum StateCacherOutcome {
+ /// The caller needs to perform the state change in the actual command buffer builder.
+ NeedChange,
+ /// The state change is not necessary.
+ AlreadyOk,
+}
+
+impl StateCacher {
+ /// Builds a new `StateCacher`.
+ #[inline]
+ pub fn new() -> StateCacher {
+ StateCacher {
+ dynamic_state: DynamicState::none(),
+ compute_pipeline: ash::vk::Pipeline::null(),
+ graphics_pipeline: ash::vk::Pipeline::null(),
+ compute_descriptor_sets: SmallVec::new(),
+ graphics_descriptor_sets: SmallVec::new(),
+ poisoned_descriptor_sets: false,
+ vertex_buffers: SmallVec::new(),
+ poisoned_vertex_buffers: false,
+ index_buffer: None,
+ }
+ }
+
+ /// Resets the cache to its default state. You **must** call this after executing a secondary
+ /// command buffer.
+ #[inline]
+ pub fn invalidate(&mut self) {
+ self.dynamic_state = DynamicState::none();
+ self.compute_pipeline = ash::vk::Pipeline::null();
+ self.graphics_pipeline = ash::vk::Pipeline::null();
+ self.compute_descriptor_sets = SmallVec::new();
+ self.graphics_descriptor_sets = SmallVec::new();
+ self.vertex_buffers = SmallVec::new();
+ self.index_buffer = None;
+ }
+
+ /// Compares the current state with `incoming`, and returns a new state that contains the
+ /// states that differ and that need to be actually set in the command buffer builder.
+ ///
+ /// This function also updates the state cacher. The state cacher assumes that the state
+ /// changes are going to be performed after this function returns.
+ pub fn dynamic_state(&mut self, incoming: &DynamicState) -> DynamicState {
+ let mut changed = DynamicState::none();
+
+ macro_rules! cmp {
+ ($field:ident) => {
+ if self.dynamic_state.$field != incoming.$field {
+ changed.$field = incoming.$field.clone();
+ if incoming.$field.is_some() {
+ self.dynamic_state.$field = incoming.$field.clone();
+ }
+ }
+ };
+ }
+
+ cmp!(line_width);
+ cmp!(viewports);
+ cmp!(scissors);
+ cmp!(compare_mask);
+ cmp!(reference);
+ cmp!(write_mask);
+
+ changed
+ }
+
+ /// Starts the process of comparing a list of descriptor sets to the descriptor sets currently
+ /// in cache.
+ ///
+ /// After calling this function, call `add` for each set one by one. Then call `compare` in
+ /// order to get the index of the first set to bind, or `None` if the sets were identical to
+ /// what is in cache.
+ ///
+ /// This process also updates the state cacher. The state cacher assumes that the state
+ /// changes are going to be performed after the `compare` function returns.
+ #[inline]
+ pub fn bind_descriptor_sets(
+ &mut self,
+ pipeline_bind_point: PipelineBindPoint,
+ ) -> StateCacherDescriptorSets {
+ if self.poisoned_descriptor_sets {
+ self.compute_descriptor_sets = SmallVec::new();
+ self.graphics_descriptor_sets = SmallVec::new();
+ }
+
+ self.poisoned_descriptor_sets = true;
+
+ StateCacherDescriptorSets {
+ poisoned: &mut self.poisoned_descriptor_sets,
+ state: match pipeline_bind_point {
+ PipelineBindPoint::Compute => &mut self.compute_descriptor_sets,
+ PipelineBindPoint::Graphics => &mut self.graphics_descriptor_sets,
+ },
+ offset: 0,
+ found_diff: None,
+ }
+ }
+
+ /// Checks whether we need to bind a graphics pipeline. Returns `StateCacherOutcome::AlreadyOk`
+ /// if the pipeline was already bound earlier, and `StateCacherOutcome::NeedChange` if you need
+ /// to actually bind the pipeline.
+ ///
+ /// This function also updates the state cacher. The state cacher assumes that the state
+ /// changes are going to be performed after this function returns.
+ pub fn bind_graphics_pipeline<P>(&mut self, pipeline: &P) -> StateCacherOutcome
+ where
+ P: GraphicsPipelineAbstract,
+ {
+ let inner = GraphicsPipelineAbstract::inner(pipeline).internal_object();
+ if inner == self.graphics_pipeline {
+ StateCacherOutcome::AlreadyOk
+ } else {
+ self.graphics_pipeline = inner;
+ StateCacherOutcome::NeedChange
+ }
+ }
+
+ /// Checks whether we need to bind a compute pipeline. Returns `StateCacherOutcome::AlreadyOk`
+ /// if the pipeline was already bound earlier, and `StateCacherOutcome::NeedChange` if you need
+ /// to actually bind the pipeline.
+ ///
+ /// This function also updates the state cacher. The state cacher assumes that the state
+ /// changes are going to be performed after this function returns.
+ pub fn bind_compute_pipeline<P>(&mut self, pipeline: &P) -> StateCacherOutcome
+ where
+ P: ComputePipelineAbstract,
+ {
+ let inner = pipeline.inner().internal_object();
+ if inner == self.compute_pipeline {
+ StateCacherOutcome::AlreadyOk
+ } else {
+ self.compute_pipeline = inner;
+ StateCacherOutcome::NeedChange
+ }
+ }
+
+ /// Starts the process of comparing a list of vertex buffers to the vertex buffers currently
+ /// in cache.
+ ///
+ /// After calling this function, call `add` for each set one by one. Then call `compare` in
+ /// order to get the range of the vertex buffers to bind, or `None` if the sets were identical
+ /// to what is in cache.
+ ///
+ /// This process also updates the state cacher. The state cacher assumes that the state
+ /// changes are going to be performed after the `compare` function returns.
+ #[inline]
+ pub fn bind_vertex_buffers(&mut self) -> StateCacherVertexBuffers {
+ if self.poisoned_vertex_buffers {
+ self.vertex_buffers = SmallVec::new();
+ }
+
+ self.poisoned_vertex_buffers = true;
+
+ StateCacherVertexBuffers {
+ poisoned: &mut self.poisoned_vertex_buffers,
+ state: &mut self.vertex_buffers,
+ offset: 0,
+ first_diff: None,
+ last_diff: 0,
+ }
+ }
+
+ /// Checks whether we need to bind an index buffer. Returns `StateCacherOutcome::AlreadyOk`
+ /// if the index buffer was already bound earlier, and `StateCacherOutcome::NeedChange` if you
+ /// need to actually bind the buffer.
+ ///
+ /// This function also updates the state cacher. The state cacher assumes that the state
+ /// changes are going to be performed after this function returns.
+ pub fn bind_index_buffer<B>(&mut self, index_buffer: &B, ty: IndexType) -> StateCacherOutcome
+ where
+ B: ?Sized + BufferAccess,
+ {
+ let value = {
+ let inner = index_buffer.inner();
+ (inner.buffer.internal_object(), inner.offset, ty)
+ };
+
+ if self.index_buffer == Some(value) {
+ StateCacherOutcome::AlreadyOk
+ } else {
+ self.index_buffer = Some(value);
+ StateCacherOutcome::NeedChange
+ }
+ }
+}
+
+/// Helper struct for comparing descriptor sets.
+///
+/// > **Note**: For reliability reasons, if you drop/leak this struct before calling `compare` then
+/// > the cache of the currently bound descriptor sets will be reset.
+pub struct StateCacherDescriptorSets<'s> {
+ // Reference to the parent's `poisoned_descriptor_sets`.
+ poisoned: &'s mut bool,
+ // Reference to the descriptor sets list to compare to.
+ state: &'s mut SmallVec<[(ash::vk::DescriptorSet, SmallVec<[u32; 32]>); 12]>,
+ // Next offset within the list to compare to.
+ offset: usize,
+ // Contains the return value of `compare`.
+ found_diff: Option<u32>,
+}
+
+impl<'s> StateCacherDescriptorSets<'s> {
+ /// Adds a descriptor set to the list to compare.
+ #[inline]
+ pub fn add(&mut self, descriptor_set: &DescriptorSetWithOffsets) {
+ let (descriptor_set, dynamic_offsets) = descriptor_set.as_ref();
+ let raw = descriptor_set.inner().internal_object();
+ let dynamic_offsets = dynamic_offsets.iter().copied().collect();
+
+ if let Some(state) = self.state.get_mut(self.offset) {
+ if (&state.0, &state.1) == (&raw, &dynamic_offsets) {
+ self.offset += 1;
+ return;
+ }
+
+ *state = (raw, dynamic_offsets);
+ } else {
+ self.state.push((raw, dynamic_offsets));
+ }
+
+ if self.found_diff.is_none() {
+ self.found_diff = Some(self.offset as u32);
+ }
+ self.offset += 1;
+ }
+
+ /// Compares your list to the list in cache, and returns the offset of the first set to bind.
+ /// Returns `None` if the two lists were identical.
+ ///
+ /// After this function returns, the cache will be updated to match your list.
+ #[inline]
+ pub fn compare(self) -> Option<u32> {
+ *self.poisoned = false;
+ // Removing from the cache any set that wasn't added with `add`.
+ self.state.truncate(self.offset);
+ self.found_diff
+ }
+}
+
+/// Helper struct for comparing vertex buffers.
+///
+/// > **Note**: For reliability reasons, if you drop/leak this struct before calling `compare` then
+/// > the cache of the currently bound vertex buffers will be reset.
+pub struct StateCacherVertexBuffers<'s> {
+ // Reference to the parent's `poisoned_vertex_buffers`.
+ poisoned: &'s mut bool,
+ // Reference to the vertex buffers list to compare to.
+ state: &'s mut SmallVec<[(ash::vk::Buffer, DeviceSize); 12]>,
+ // Next offset within the list to compare to.
+ offset: usize,
+ // Contains the offset of the first vertex buffer that differs.
+ first_diff: Option<u32>,
+ // Offset of the last vertex buffer that differs.
+ last_diff: u32,
+}
+
+impl<'s> StateCacherVertexBuffers<'s> {
+ /// Adds a vertex buffer to the list to compare.
+ #[inline]
+ pub fn add<B>(&mut self, buffer: &B)
+ where
+ B: ?Sized + BufferAccess,
+ {
+ let raw = {
+ let inner = buffer.inner();
+ let raw = inner.buffer.internal_object();
+ let offset = inner.offset;
+ (raw, offset)
+ };
+
+ if self.offset < self.state.len() {
+ if self.state[self.offset] == raw {
+ self.offset += 1;
+ return;
+ }
+
+ self.state[self.offset] = raw;
+ } else {
+ self.state.push(raw);
+ }
+
+ self.last_diff = self.offset as u32;
+ if self.first_diff.is_none() {
+ self.first_diff = Some(self.offset as u32);
+ }
+ self.offset += 1;
+ }
+
+ /// Compares your list to the list in cache, and returns the range of the vertex buffers to
+ /// bind. Returns `None` if the two lists were identical.
+ ///
+ /// After this function returns, the cache will be updated to match your list.
+ ///
+ /// > **Note**: Keep in mind that `range.end` is *after* the last element. For example the
+ /// > range `1 .. 2` only contains one element.
+ #[inline]
+ pub fn compare(self) -> Option<Range<u32>> {
+ *self.poisoned = false;
+
+ // Removing from the cache any set that wasn't added with `add`.
+ self.state.truncate(self.offset);
+
+ self.first_diff.map(|first| {
+ debug_assert!(first <= self.last_diff);
+ first..(self.last_diff + 1)
+ })
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use crate::buffer::BufferUsage;
+ use crate::buffer::CpuAccessibleBuffer;
+ use crate::command_buffer::state_cacher::StateCacher;
+
+ #[test]
+ fn vb_caching_single() {
+ let (device, queue) = gfx_dev_and_queue!();
+
+ const EMPTY: [i32; 0] = [];
+ let buf =
+ CpuAccessibleBuffer::from_data(device, BufferUsage::vertex_buffer(), false, EMPTY)
+ .unwrap();
+
+ let mut cacher = StateCacher::new();
+
+ {
+ let mut bind_vb = cacher.bind_vertex_buffers();
+ bind_vb.add(&buf);
+ assert_eq!(bind_vb.compare(), Some(0..1));
+ }
+
+ for _ in 0..3 {
+ let mut bind_vb = cacher.bind_vertex_buffers();
+ bind_vb.add(&buf);
+ assert_eq!(bind_vb.compare(), None);
+ }
+ }
+
+ #[test]
+ fn vb_caching_invalidated() {
+ let (device, queue) = gfx_dev_and_queue!();
+
+ const EMPTY: [i32; 0] = [];
+ let buf =
+ CpuAccessibleBuffer::from_data(device, BufferUsage::vertex_buffer(), false, EMPTY)
+ .unwrap();
+
+ let mut cacher = StateCacher::new();
+
+ {
+ let mut bind_vb = cacher.bind_vertex_buffers();
+ bind_vb.add(&buf);
+ assert_eq!(bind_vb.compare(), Some(0..1));
+ }
+
+ {
+ let mut bind_vb = cacher.bind_vertex_buffers();
+ bind_vb.add(&buf);
+ assert_eq!(bind_vb.compare(), None);
+ }
+
+ cacher.invalidate();
+
+ {
+ let mut bind_vb = cacher.bind_vertex_buffers();
+ bind_vb.add(&buf);
+ assert_eq!(bind_vb.compare(), Some(0..1));
+ }
+ }
+
+ #[test]
+ fn vb_caching_multi() {
+ let (device, queue) = gfx_dev_and_queue!();
+
+ const EMPTY: [i32; 0] = [];
+ let buf1 = CpuAccessibleBuffer::from_data(
+ device.clone(),
+ BufferUsage::vertex_buffer(),
+ false,
+ EMPTY,
+ )
+ .unwrap();
+ let buf2 = CpuAccessibleBuffer::from_data(
+ device.clone(),
+ BufferUsage::vertex_buffer(),
+ false,
+ EMPTY,
+ )
+ .unwrap();
+ let buf3 =
+ CpuAccessibleBuffer::from_data(device, BufferUsage::vertex_buffer(), false, EMPTY)
+ .unwrap();
+
+ let mut cacher = StateCacher::new();
+
+ {
+ let mut bind_vb = cacher.bind_vertex_buffers();
+ bind_vb.add(&buf1);
+ bind_vb.add(&buf2);
+ assert_eq!(bind_vb.compare(), Some(0..2));
+ }
+
+ {
+ let mut bind_vb = cacher.bind_vertex_buffers();
+ bind_vb.add(&buf1);
+ bind_vb.add(&buf2);
+ bind_vb.add(&buf3);
+ assert_eq!(bind_vb.compare(), Some(2..3));
+ }
+
+ {
+ let mut bind_vb = cacher.bind_vertex_buffers();
+ bind_vb.add(&buf1);
+ assert_eq!(bind_vb.compare(), None);
+ }
+
+ {
+ let mut bind_vb = cacher.bind_vertex_buffers();
+ bind_vb.add(&buf1);
+ bind_vb.add(&buf3);
+ assert_eq!(bind_vb.compare(), Some(1..2));
+ }
+
+ {
+ let mut bind_vb = cacher.bind_vertex_buffers();
+ bind_vb.add(&buf2);
+ bind_vb.add(&buf3);
+ assert_eq!(bind_vb.compare(), Some(0..1));
+ }
+ }
+}