summaryrefslogtreecommitdiffstats
path: root/gfx/wgpu/wgpu-core/src/track
diff options
context:
space:
mode:
Diffstat (limited to 'gfx/wgpu/wgpu-core/src/track')
-rw-r--r--gfx/wgpu/wgpu-core/src/track/buffer.rs241
-rw-r--r--gfx/wgpu/wgpu-core/src/track/mod.rs593
-rw-r--r--gfx/wgpu/wgpu-core/src/track/range.rs399
-rw-r--r--gfx/wgpu/wgpu-core/src/track/texture.rs466
4 files changed, 1699 insertions, 0 deletions
diff --git a/gfx/wgpu/wgpu-core/src/track/buffer.rs b/gfx/wgpu/wgpu-core/src/track/buffer.rs
new file mode 100644
index 0000000000..e4999a9ae4
--- /dev/null
+++ b/gfx/wgpu/wgpu-core/src/track/buffer.rs
@@ -0,0 +1,241 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use super::{PendingTransition, ResourceState, Unit};
+use crate::{
+ id::{BufferId, Valid},
+ resource::BufferUse,
+};
+
+//TODO: store `hal::buffer::State` here to avoid extra conversions
+pub(crate) type BufferState = Unit<BufferUse>;
+
+impl PendingTransition<BufferState> {
+ fn collapse(self) -> Result<BufferUse, Self> {
+ if self.usage.start.is_empty()
+ || self.usage.start == self.usage.end
+ || !BufferUse::WRITE_ALL.intersects(self.usage.start | self.usage.end)
+ {
+ Ok(self.usage.start | self.usage.end)
+ } else {
+ Err(self)
+ }
+ }
+}
+
+impl Default for BufferState {
+ fn default() -> Self {
+ Self {
+ first: None,
+ last: BufferUse::empty(),
+ }
+ }
+}
+
+impl BufferState {
+ pub fn with_usage(usage: BufferUse) -> Self {
+ Unit::new(usage)
+ }
+}
+
+impl ResourceState for BufferState {
+ type Id = BufferId;
+ type Selector = ();
+ type Usage = BufferUse;
+
+ fn query(&self, _selector: Self::Selector) -> Option<Self::Usage> {
+ Some(self.last)
+ }
+
+ fn change(
+ &mut self,
+ id: Valid<Self::Id>,
+ _selector: Self::Selector,
+ usage: Self::Usage,
+ output: Option<&mut Vec<PendingTransition<Self>>>,
+ ) -> Result<(), PendingTransition<Self>> {
+ let old = self.last;
+ if old != usage || !BufferUse::ORDERED.contains(usage) {
+ let pending = PendingTransition {
+ id,
+ selector: (),
+ usage: old..usage,
+ };
+ *self = match output {
+ None => {
+ assert_eq!(
+ self.first, None,
+ "extending a state that is already a transition"
+ );
+ Unit::new(pending.collapse()?)
+ }
+ Some(transitions) => {
+ transitions.push(pending);
+ Unit {
+ first: self.first.or(Some(old)),
+ last: usage,
+ }
+ }
+ };
+ }
+ Ok(())
+ }
+
+ fn prepend(
+ &mut self,
+ id: Valid<Self::Id>,
+ _selector: Self::Selector,
+ usage: Self::Usage,
+ ) -> Result<(), PendingTransition<Self>> {
+ match self.first {
+ Some(old) if old != usage => Err(PendingTransition {
+ id,
+ selector: (),
+ usage: old..usage,
+ }),
+ _ => {
+ self.first = Some(usage);
+ Ok(())
+ }
+ }
+ }
+
+ fn merge(
+ &mut self,
+ id: Valid<Self::Id>,
+ other: &Self,
+ output: Option<&mut Vec<PendingTransition<Self>>>,
+ ) -> Result<(), PendingTransition<Self>> {
+ let old = self.last;
+ let new = other.port();
+ if old == new && BufferUse::ORDERED.contains(new) {
+ if output.is_some() && self.first.is_none() {
+ self.first = Some(old);
+ }
+ } else {
+ let pending = PendingTransition {
+ id,
+ selector: (),
+ usage: old..new,
+ };
+ *self = match output {
+ None => {
+ assert_eq!(
+ self.first, None,
+ "extending a state that is already a transition"
+ );
+ Unit::new(pending.collapse()?)
+ }
+ Some(transitions) => {
+ transitions.push(pending);
+ Unit {
+ first: self.first.or(Some(old)),
+ last: other.last,
+ }
+ }
+ };
+ }
+ Ok(())
+ }
+
+ fn optimize(&mut self) {}
+}
+
+#[cfg(test)]
+mod test {
+ use super::*;
+ use crate::id::Id;
+
+ #[test]
+ fn change_extend() {
+ let mut bs = Unit {
+ first: None,
+ last: BufferUse::INDEX,
+ };
+ let id = Id::dummy();
+ assert_eq!(
+ bs.change(id, (), BufferUse::STORAGE_STORE, None),
+ Err(PendingTransition {
+ id,
+ selector: (),
+ usage: BufferUse::INDEX..BufferUse::STORAGE_STORE,
+ }),
+ );
+ bs.change(id, (), BufferUse::VERTEX, None).unwrap();
+ bs.change(id, (), BufferUse::INDEX, None).unwrap();
+ assert_eq!(bs, Unit::new(BufferUse::VERTEX | BufferUse::INDEX));
+ }
+
+ #[test]
+ fn change_replace() {
+ let mut bs = Unit {
+ first: None,
+ last: BufferUse::STORAGE_STORE,
+ };
+ let id = Id::dummy();
+ let mut list = Vec::new();
+ bs.change(id, (), BufferUse::VERTEX, Some(&mut list))
+ .unwrap();
+ assert_eq!(
+ &list,
+ &[PendingTransition {
+ id,
+ selector: (),
+ usage: BufferUse::STORAGE_STORE..BufferUse::VERTEX,
+ }],
+ );
+ assert_eq!(
+ bs,
+ Unit {
+ first: Some(BufferUse::STORAGE_STORE),
+ last: BufferUse::VERTEX,
+ }
+ );
+
+ list.clear();
+ bs.change(id, (), BufferUse::STORAGE_STORE, Some(&mut list))
+ .unwrap();
+ assert_eq!(
+ &list,
+ &[PendingTransition {
+ id,
+ selector: (),
+ usage: BufferUse::VERTEX..BufferUse::STORAGE_STORE,
+ }],
+ );
+ assert_eq!(
+ bs,
+ Unit {
+ first: Some(BufferUse::STORAGE_STORE),
+ last: BufferUse::STORAGE_STORE,
+ }
+ );
+ }
+
+ #[test]
+ fn prepend() {
+ let mut bs = Unit {
+ first: None,
+ last: BufferUse::VERTEX,
+ };
+ let id = Id::dummy();
+ bs.prepend(id, (), BufferUse::INDEX).unwrap();
+ bs.prepend(id, (), BufferUse::INDEX).unwrap();
+ assert_eq!(
+ bs.prepend(id, (), BufferUse::STORAGE_LOAD),
+ Err(PendingTransition {
+ id,
+ selector: (),
+ usage: BufferUse::INDEX..BufferUse::STORAGE_LOAD,
+ })
+ );
+ assert_eq!(
+ bs,
+ Unit {
+ first: Some(BufferUse::INDEX),
+ last: BufferUse::VERTEX,
+ }
+ );
+ }
+}
diff --git a/gfx/wgpu/wgpu-core/src/track/mod.rs b/gfx/wgpu/wgpu-core/src/track/mod.rs
new file mode 100644
index 0000000000..6d7e908ef6
--- /dev/null
+++ b/gfx/wgpu/wgpu-core/src/track/mod.rs
@@ -0,0 +1,593 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+mod buffer;
+mod range;
+mod texture;
+
+use crate::{
+ conv, hub,
+ id::{self, TypedId, Valid},
+ resource, Epoch, FastHashMap, Index, RefCount,
+};
+
+use std::{collections::hash_map::Entry, fmt, marker::PhantomData, ops, vec::Drain};
+use thiserror::Error;
+
+pub(crate) use buffer::BufferState;
+pub(crate) use texture::{TextureSelector, TextureState};
+
+/// A single unit of state tracking. It keeps an initial
+/// usage as well as the last/current one, similar to `Range`.
+#[derive(Clone, Copy, Debug, PartialEq)]
+pub struct Unit<U> {
+ first: Option<U>,
+ last: U,
+}
+
+impl<U: Copy> Unit<U> {
+ /// Create a new unit from a given usage.
+ fn new(usage: U) -> Self {
+ Self {
+ first: None,
+ last: usage,
+ }
+ }
+
+ /// Return a usage to link to.
+ fn port(&self) -> U {
+ self.first.unwrap_or(self.last)
+ }
+}
+
+/// The main trait that abstracts away the tracking logic of
+/// a particular resource type, like a buffer or a texture.
+pub(crate) trait ResourceState: Clone + Default {
+ /// Corresponding `HUB` identifier.
+ type Id: Copy + fmt::Debug + TypedId;
+ /// A type specifying the sub-resources.
+ type Selector: fmt::Debug;
+ /// Usage type for a `Unit` of a sub-resource.
+ type Usage: fmt::Debug;
+
+ /// Check if all the selected sub-resources have the same
+ /// usage, and return it.
+ ///
+ /// Returns `None` if no sub-resources
+ /// are intersecting with the selector, or their usage
+ /// isn't consistent.
+ fn query(&self, selector: Self::Selector) -> Option<Self::Usage>;
+
+ /// Change the last usage of the selected sub-resources.
+ ///
+ /// If `output` is specified, it's filled with the
+ /// `PendingTransition` objects corresponding to smaller
+ /// sub-resource transitions. The old usage is replaced by
+ /// the new one.
+ ///
+ /// If `output` is `None`, the old usage is extended with
+ /// the new usage. The error is returned if it's not possible,
+ /// specifying the conflicting transition. Extension can only
+ /// be done for read-only usages.
+ fn change(
+ &mut self,
+ id: Valid<Self::Id>,
+ selector: Self::Selector,
+ usage: Self::Usage,
+ output: Option<&mut Vec<PendingTransition<Self>>>,
+ ) -> Result<(), PendingTransition<Self>>;
+
+ /// Sets up the first usage of the selected sub-resources.
+ fn prepend(
+ &mut self,
+ id: Valid<Self::Id>,
+ selector: Self::Selector,
+ usage: Self::Usage,
+ ) -> Result<(), PendingTransition<Self>>;
+
+ /// Merge the state of this resource tracked by a different instance
+ /// with the current one.
+ ///
+ /// Same rules for `output` apply as with `change()`: last usage state
+ /// is either replaced (when `output` is provided) with a
+ /// `PendingTransition` pushed to this vector, or extended with the
+ /// other read-only usage, unless there is a usage conflict, and
+ /// the error is generated (returning the conflict).
+ fn merge(
+ &mut self,
+ id: Valid<Self::Id>,
+ other: &Self,
+ output: Option<&mut Vec<PendingTransition<Self>>>,
+ ) -> Result<(), PendingTransition<Self>>;
+
+ /// Try to optimize the internal representation.
+ fn optimize(&mut self);
+}
+
+/// Structure wrapping the abstract tracking state with the relevant resource
+/// data, such as the reference count and the epoch.
+#[derive(Clone)]
+struct Resource<S> {
+ ref_count: RefCount,
+ state: S,
+ epoch: Epoch,
+}
+
+/// A structure containing all the information about a particular resource
+/// transition. User code should be able to generate a pipeline barrier
+/// based on the contents.
+#[derive(Debug, PartialEq)]
+pub(crate) struct PendingTransition<S: ResourceState> {
+ pub id: Valid<S::Id>,
+ pub selector: S::Selector,
+ pub usage: ops::Range<S::Usage>,
+}
+
+impl PendingTransition<BufferState> {
+ /// Produce the gfx-hal barrier corresponding to the transition.
+ pub fn into_hal<'a, B: hal::Backend>(
+ self,
+ buf: &'a resource::Buffer<B>,
+ ) -> hal::memory::Barrier<'a, B> {
+ tracing::trace!("\tbuffer -> {:?}", self);
+ let &(ref target, _) = buf.raw.as_ref().expect("Buffer is destroyed");
+ hal::memory::Barrier::Buffer {
+ states: conv::map_buffer_state(self.usage.start)
+ ..conv::map_buffer_state(self.usage.end),
+ target,
+ range: hal::buffer::SubRange::WHOLE,
+ families: None,
+ }
+ }
+}
+
+impl PendingTransition<TextureState> {
+ /// Produce the gfx-hal barrier corresponding to the transition.
+ pub fn into_hal<'a, B: hal::Backend>(
+ self,
+ tex: &'a resource::Texture<B>,
+ ) -> hal::memory::Barrier<'a, B> {
+ tracing::trace!("\ttexture -> {:?}", self);
+ let &(ref target, _) = tex.raw.as_ref().expect("Texture is destroyed");
+ let aspects = tex.aspects;
+ hal::memory::Barrier::Image {
+ states: conv::map_texture_state(self.usage.start, aspects)
+ ..conv::map_texture_state(self.usage.end, aspects),
+ target,
+ range: hal::image::SubresourceRange {
+ aspects,
+ level_start: self.selector.levels.start,
+ level_count: Some(self.selector.levels.end - self.selector.levels.start),
+ layer_start: self.selector.layers.start,
+ layer_count: Some(self.selector.layers.end - self.selector.layers.start),
+ },
+ families: None,
+ }
+ }
+}
+
+#[derive(Clone, Debug, Error)]
+pub enum UseExtendError<U: fmt::Debug> {
+ #[error("resource is invalid")]
+ InvalidResource,
+ #[error("total usage {0:?} is not valid")]
+ Conflict(U),
+}
+
+/// A tracker for all resources of a given type.
+pub(crate) struct ResourceTracker<S: ResourceState> {
+ /// An association of known resource indices with their tracked states.
+ map: FastHashMap<Index, Resource<S>>,
+ /// Temporary storage for collecting transitions.
+ temp: Vec<PendingTransition<S>>,
+ /// The backend variant for all the tracked resources.
+ backend: wgt::Backend,
+}
+
+impl<S: ResourceState + fmt::Debug> fmt::Debug for ResourceTracker<S> {
+ fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
+ self.map
+ .iter()
+ .map(|(&index, res)| ((index, res.epoch), &res.state))
+ .collect::<FastHashMap<_, _>>()
+ .fmt(formatter)
+ }
+}
+
+impl<S: ResourceState> ResourceTracker<S> {
+ /// Create a new empty tracker.
+ pub fn new(backend: wgt::Backend) -> Self {
+ Self {
+ map: FastHashMap::default(),
+ temp: Vec::new(),
+ backend,
+ }
+ }
+
+ /// Remove an id from the tracked map.
+ pub(crate) fn remove(&mut self, id: Valid<S::Id>) -> bool {
+ let (index, epoch, backend) = id.0.unzip();
+ debug_assert_eq!(backend, self.backend);
+ match self.map.remove(&index) {
+ Some(resource) => {
+ assert_eq!(resource.epoch, epoch);
+ true
+ }
+ None => false,
+ }
+ }
+
+ /// Removes the resource from the tracker if we are holding the last reference.
+ pub(crate) fn remove_abandoned(&mut self, id: Valid<S::Id>) -> bool {
+ let (index, epoch, backend) = id.0.unzip();
+ debug_assert_eq!(backend, self.backend);
+ match self.map.entry(index) {
+ Entry::Occupied(e) => {
+ if e.get().ref_count.load() == 1 {
+ let res = e.remove();
+ assert_eq!(res.epoch, epoch);
+ true
+ } else {
+ false
+ }
+ }
+ _ => false,
+ }
+ }
+
+ /// Try to optimize the internal representation.
+ pub(crate) fn optimize(&mut self) {
+ for resource in self.map.values_mut() {
+ resource.state.optimize();
+ }
+ }
+
+ /// Return an iterator over used resources keys.
+ pub fn used<'a>(&'a self) -> impl 'a + Iterator<Item = Valid<S::Id>> {
+ let backend = self.backend;
+ self.map
+ .iter()
+ .map(move |(&index, resource)| Valid(S::Id::zip(index, resource.epoch, backend)))
+ }
+
+ /// Clear the tracked contents.
+ fn clear(&mut self) {
+ self.map.clear();
+ }
+
+ /// Initialize a resource to be used.
+ ///
+ /// Returns false if the resource is already registered.
+ pub(crate) fn init(
+ &mut self,
+ id: Valid<S::Id>,
+ ref_count: RefCount,
+ state: S,
+ ) -> Result<(), &S> {
+ let (index, epoch, backend) = id.0.unzip();
+ debug_assert_eq!(backend, self.backend);
+ match self.map.entry(index) {
+ Entry::Vacant(e) => {
+ e.insert(Resource {
+ ref_count,
+ state,
+ epoch,
+ });
+ Ok(())
+ }
+ Entry::Occupied(e) => Err(&e.into_mut().state),
+ }
+ }
+
+ /// Query the usage of a resource selector.
+ ///
+ /// Returns `Some(Usage)` only if this usage is consistent
+ /// across the given selector.
+ pub fn query(&self, id: Valid<S::Id>, selector: S::Selector) -> Option<S::Usage> {
+ let (index, epoch, backend) = id.0.unzip();
+ debug_assert_eq!(backend, self.backend);
+ let res = self.map.get(&index)?;
+ assert_eq!(res.epoch, epoch);
+ res.state.query(selector)
+ }
+
+ /// Make sure that a resource is tracked, and return a mutable
+ /// reference to it.
+ fn get_or_insert<'a>(
+ self_backend: wgt::Backend,
+ map: &'a mut FastHashMap<Index, Resource<S>>,
+ id: Valid<S::Id>,
+ ref_count: &RefCount,
+ ) -> &'a mut Resource<S> {
+ let (index, epoch, backend) = id.0.unzip();
+ debug_assert_eq!(self_backend, backend);
+ match map.entry(index) {
+ Entry::Vacant(e) => e.insert(Resource {
+ ref_count: ref_count.clone(),
+ state: S::default(),
+ epoch,
+ }),
+ Entry::Occupied(e) => {
+ assert_eq!(e.get().epoch, epoch);
+ e.into_mut()
+ }
+ }
+ }
+
+ /// Extend the usage of a specified resource.
+ ///
+ /// Returns conflicting transition as an error.
+ pub(crate) fn change_extend(
+ &mut self,
+ id: Valid<S::Id>,
+ ref_count: &RefCount,
+ selector: S::Selector,
+ usage: S::Usage,
+ ) -> Result<(), PendingTransition<S>> {
+ Self::get_or_insert(self.backend, &mut self.map, id, ref_count)
+ .state
+ .change(id, selector, usage, None)
+ }
+
+ /// Replace the usage of a specified resource.
+ pub(crate) fn change_replace(
+ &mut self,
+ id: Valid<S::Id>,
+ ref_count: &RefCount,
+ selector: S::Selector,
+ usage: S::Usage,
+ ) -> Drain<PendingTransition<S>> {
+ let res = Self::get_or_insert(self.backend, &mut self.map, id, ref_count);
+ res.state
+ .change(id, selector, usage, Some(&mut self.temp))
+ .ok(); //TODO: unwrap?
+ self.temp.drain(..)
+ }
+
+ /// Turn the tracking from the "expand" mode into the "replace" one,
+ /// installing the selected usage as the "first".
+ /// This is a special operation only used by the render pass attachments.
+ pub(crate) fn prepend(
+ &mut self,
+ id: Valid<S::Id>,
+ ref_count: &RefCount,
+ selector: S::Selector,
+ usage: S::Usage,
+ ) -> Result<(), PendingTransition<S>> {
+ Self::get_or_insert(self.backend, &mut self.map, id, ref_count)
+ .state
+ .prepend(id, selector, usage)
+ }
+
+ /// Merge another tracker into `self` by extending the current states
+ /// without any transitions.
+ pub(crate) fn merge_extend(&mut self, other: &Self) -> Result<(), PendingTransition<S>> {
+ debug_assert_eq!(self.backend, other.backend);
+ for (&index, new) in other.map.iter() {
+ match self.map.entry(index) {
+ Entry::Vacant(e) => {
+ e.insert(new.clone());
+ }
+ Entry::Occupied(e) => {
+ assert_eq!(e.get().epoch, new.epoch);
+ let id = Valid(S::Id::zip(index, new.epoch, self.backend));
+ e.into_mut().state.merge(id, &new.state, None)?;
+ }
+ }
+ }
+ Ok(())
+ }
+
+ /// Merge another tracker, adding it's transitions to `self`.
+ /// Transitions the current usage to the new one.
+ pub(crate) fn merge_replace<'a>(&'a mut self, other: &'a Self) -> Drain<PendingTransition<S>> {
+ for (&index, new) in other.map.iter() {
+ match self.map.entry(index) {
+ Entry::Vacant(e) => {
+ e.insert(new.clone());
+ }
+ Entry::Occupied(e) => {
+ assert_eq!(e.get().epoch, new.epoch);
+ let id = Valid(S::Id::zip(index, new.epoch, self.backend));
+ e.into_mut()
+ .state
+ .merge(id, &new.state, Some(&mut self.temp))
+ .ok(); //TODO: unwrap?
+ }
+ }
+ }
+ self.temp.drain(..)
+ }
+
+ /// Use a given resource provided by an `Id` with the specified usage.
+ /// Combines storage access by 'Id' with the transition that extends
+ /// the last read-only usage, if possible.
+ ///
+ /// Returns the old usage as an error if there is a conflict.
+ pub(crate) fn use_extend<'a, T: 'a + hub::Resource>(
+ &mut self,
+ storage: &'a hub::Storage<T, S::Id>,
+ id: S::Id,
+ selector: S::Selector,
+ usage: S::Usage,
+ ) -> Result<&'a T, UseExtendError<S::Usage>> {
+ let item = storage
+ .get(id)
+ .map_err(|_| UseExtendError::InvalidResource)?;
+ self.change_extend(
+ Valid(id),
+ item.life_guard().ref_count.as_ref().unwrap(),
+ selector,
+ usage,
+ )
+ .map(|()| item)
+ .map_err(|pending| UseExtendError::Conflict(pending.usage.end))
+ }
+
+ /// Use a given resource provided by an `Id` with the specified usage.
+ /// Combines storage access by 'Id' with the transition that replaces
+ /// the last usage with a new one, returning an iterator over these
+ /// transitions.
+ pub(crate) fn use_replace<'a, T: 'a + hub::Resource>(
+ &mut self,
+ storage: &'a hub::Storage<T, S::Id>,
+ id: S::Id,
+ selector: S::Selector,
+ usage: S::Usage,
+ ) -> Result<(&'a T, Drain<PendingTransition<S>>), S::Id> {
+ let item = storage.get(id).map_err(|_| id)?;
+ let drain = self.change_replace(
+ Valid(id),
+ item.life_guard().ref_count.as_ref().unwrap(),
+ selector,
+ usage,
+ );
+ Ok((item, drain))
+ }
+}
+
+impl<I: Copy + fmt::Debug + TypedId> ResourceState for PhantomData<I> {
+ type Id = I;
+ type Selector = ();
+ type Usage = ();
+
+ fn query(&self, _selector: Self::Selector) -> Option<Self::Usage> {
+ Some(())
+ }
+
+ fn change(
+ &mut self,
+ _id: Valid<Self::Id>,
+ _selector: Self::Selector,
+ _usage: Self::Usage,
+ _output: Option<&mut Vec<PendingTransition<Self>>>,
+ ) -> Result<(), PendingTransition<Self>> {
+ Ok(())
+ }
+
+ fn prepend(
+ &mut self,
+ _id: Valid<Self::Id>,
+ _selector: Self::Selector,
+ _usage: Self::Usage,
+ ) -> Result<(), PendingTransition<Self>> {
+ Ok(())
+ }
+
+ fn merge(
+ &mut self,
+ _id: Valid<Self::Id>,
+ _other: &Self,
+ _output: Option<&mut Vec<PendingTransition<Self>>>,
+ ) -> Result<(), PendingTransition<Self>> {
+ Ok(())
+ }
+
+ fn optimize(&mut self) {}
+}
+
+pub const DUMMY_SELECTOR: () = ();
+
+#[derive(Clone, Debug, Error)]
+pub enum UsageConflict {
+ #[error(
+ "Attempted to use buffer {id:?} as a combination of {combined_use:?} within a usage scope."
+ )]
+ Buffer {
+ id: id::BufferId,
+ combined_use: resource::BufferUse,
+ },
+ #[error("Attempted to use texture {id:?} mips {mip_levels:?} layers {array_layers:?} as a combination of {combined_use:?} within a usage scope.")]
+ Texture {
+ id: id::TextureId,
+ mip_levels: ops::Range<u32>,
+ array_layers: ops::Range<u32>,
+ combined_use: resource::TextureUse,
+ },
+}
+
+/// A set of trackers for all relevant resources.
+#[derive(Debug)]
+pub(crate) struct TrackerSet {
+ pub buffers: ResourceTracker<BufferState>,
+ pub textures: ResourceTracker<TextureState>,
+ pub views: ResourceTracker<PhantomData<id::TextureViewId>>,
+ pub bind_groups: ResourceTracker<PhantomData<id::BindGroupId>>,
+ pub samplers: ResourceTracker<PhantomData<id::SamplerId>>,
+ pub compute_pipes: ResourceTracker<PhantomData<id::ComputePipelineId>>,
+ pub render_pipes: ResourceTracker<PhantomData<id::RenderPipelineId>>,
+ pub bundles: ResourceTracker<PhantomData<id::RenderBundleId>>,
+}
+
+impl TrackerSet {
+ /// Create an empty set.
+ pub fn new(backend: wgt::Backend) -> Self {
+ Self {
+ buffers: ResourceTracker::new(backend),
+ textures: ResourceTracker::new(backend),
+ views: ResourceTracker::new(backend),
+ bind_groups: ResourceTracker::new(backend),
+ samplers: ResourceTracker::new(backend),
+ compute_pipes: ResourceTracker::new(backend),
+ render_pipes: ResourceTracker::new(backend),
+ bundles: ResourceTracker::new(backend),
+ }
+ }
+
+ /// Clear all the trackers.
+ pub fn clear(&mut self) {
+ self.buffers.clear();
+ self.textures.clear();
+ self.views.clear();
+ self.bind_groups.clear();
+ self.samplers.clear();
+ self.compute_pipes.clear();
+ self.render_pipes.clear();
+ self.bundles.clear();
+ }
+
+ /// Try to optimize the tracking representation.
+ pub fn optimize(&mut self) {
+ self.buffers.optimize();
+ self.textures.optimize();
+ self.views.optimize();
+ self.bind_groups.optimize();
+ self.samplers.optimize();
+ self.compute_pipes.optimize();
+ self.render_pipes.optimize();
+ self.bundles.optimize();
+ }
+
+ /// Merge all the trackers of another instance by extending
+ /// the usage. Panics on a conflict.
+ pub fn merge_extend(&mut self, other: &Self) -> Result<(), UsageConflict> {
+ self.buffers
+ .merge_extend(&other.buffers)
+ .map_err(|e| UsageConflict::Buffer {
+ id: e.id.0,
+ combined_use: e.usage.end,
+ })?;
+ self.textures
+ .merge_extend(&other.textures)
+ .map_err(|e| UsageConflict::Texture {
+ id: e.id.0,
+ mip_levels: e.selector.levels.start as u32..e.selector.levels.end as u32,
+ array_layers: e.selector.layers.start as u32..e.selector.layers.end as u32,
+ combined_use: e.usage.end,
+ })?;
+ self.views.merge_extend(&other.views).unwrap();
+ self.bind_groups.merge_extend(&other.bind_groups).unwrap();
+ self.samplers.merge_extend(&other.samplers).unwrap();
+ self.compute_pipes
+ .merge_extend(&other.compute_pipes)
+ .unwrap();
+ self.render_pipes.merge_extend(&other.render_pipes).unwrap();
+ self.bundles.merge_extend(&other.bundles).unwrap();
+ Ok(())
+ }
+
+ pub fn backend(&self) -> wgt::Backend {
+ self.buffers.backend
+ }
+}
diff --git a/gfx/wgpu/wgpu-core/src/track/range.rs b/gfx/wgpu/wgpu-core/src/track/range.rs
new file mode 100644
index 0000000000..458861e1a9
--- /dev/null
+++ b/gfx/wgpu/wgpu-core/src/track/range.rs
@@ -0,0 +1,399 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use smallvec::SmallVec;
+
+use std::{cmp::Ordering, fmt::Debug, iter, ops::Range, slice::Iter};
+
+/// Structure that keeps track of a I -> T mapping,
+/// optimized for a case where keys of the same values
+/// are often grouped together linearly.
+#[derive(Clone, Debug, PartialEq)]
+pub struct RangedStates<I, T> {
+ /// List of ranges, each associated with a singe value.
+ /// Ranges of keys have to be non-intersecting and ordered.
+ ranges: SmallVec<[(Range<I>, T); 1]>,
+}
+
+impl<I: Copy + PartialOrd, T: Copy + PartialEq> RangedStates<I, T> {
+ pub fn empty() -> Self {
+ Self {
+ ranges: SmallVec::new(),
+ }
+ }
+
+ pub fn from_range(range: Range<I>, value: T) -> Self {
+ Self {
+ ranges: iter::once((range, value)).collect(),
+ }
+ }
+
+ /// Construct a new instance from a slice of ranges.
+ #[cfg(test)]
+ pub fn from_slice(values: &[(Range<I>, T)]) -> Self {
+ Self {
+ ranges: values.iter().cloned().collect(),
+ }
+ }
+
+ /// Clear all the ranges.
+ pub fn clear(&mut self) {
+ self.ranges.clear();
+ }
+
+ /// Append a range.
+ ///
+ /// Assumes that the object is being constructed from a set of
+ /// ranges, and they are given in the ascending order of their keys.
+ pub fn append(&mut self, index: Range<I>, value: T) {
+ if let Some(last) = self.ranges.last() {
+ debug_assert!(last.0.end <= index.start);
+ }
+ self.ranges.push((index, value));
+ }
+
+ /// Check that all the ranges are non-intersecting and ordered.
+ /// Panics otherwise.
+ #[cfg(test)]
+ fn check_sanity(&self) {
+ for a in self.ranges.iter() {
+ assert!(a.0.start < a.0.end);
+ }
+ for (a, b) in self.ranges.iter().zip(self.ranges[1..].iter()) {
+ assert!(a.0.end <= b.0.start);
+ }
+ }
+
+ /// Merge the neighboring ranges together, where possible.
+ pub fn coalesce(&mut self) {
+ let mut num_removed = 0;
+ let mut iter = self.ranges.iter_mut();
+ let mut cur = match iter.next() {
+ Some(elem) => elem,
+ None => return,
+ };
+ for next in iter {
+ if cur.0.end == next.0.start && cur.1 == next.1 {
+ num_removed += 1;
+ cur.0.end = next.0.end;
+ next.0.end = next.0.start;
+ } else {
+ cur = next;
+ }
+ }
+ if num_removed != 0 {
+ self.ranges.retain(|pair| pair.0.start != pair.0.end);
+ }
+ }
+
+ /// Check if all intersecting ranges have the same value, which is returned.
+ ///
+ /// Returns `None` if no intersections are detected.
+ /// Returns `Some(Err)` if the intersected values are inconsistent.
+ pub fn query<U: PartialEq>(
+ &self,
+ index: &Range<I>,
+ fun: impl Fn(&T) -> U,
+ ) -> Option<Result<U, ()>> {
+ let mut result = None;
+ for &(ref range, ref value) in self.ranges.iter() {
+ if range.end > index.start && range.start < index.end {
+ let old = result.replace(fun(value));
+ if old.is_some() && old != result {
+ return Some(Err(()));
+ }
+ }
+ }
+ result.map(Ok)
+ }
+
+ /// Split the storage ranges in such a way that there is a linear subset of
+ /// them occupying exactly `index` range, which is returned mutably.
+ ///
+ /// Gaps in the ranges are filled with `default` value.
+ pub fn isolate(&mut self, index: &Range<I>, default: T) -> &mut [(Range<I>, T)] {
+ //TODO: implement this in 2 passes:
+ // 1. scan the ranges to figure out how many extra ones need to be inserted
+ // 2. go through the ranges by moving them them to the right and inserting the missing ones
+
+ let mut start_pos = match self.ranges.iter().position(|pair| pair.0.end > index.start) {
+ Some(pos) => pos,
+ None => {
+ let pos = self.ranges.len();
+ self.ranges.push((index.clone(), default));
+ return &mut self.ranges[pos..];
+ }
+ };
+
+ {
+ let (range, value) = self.ranges[start_pos].clone();
+ if range.start < index.start {
+ self.ranges[start_pos].0.start = index.start;
+ self.ranges
+ .insert(start_pos, (range.start..index.start, value));
+ start_pos += 1;
+ }
+ }
+ let mut pos = start_pos;
+ let mut range_pos = index.start;
+ loop {
+ let (range, value) = self.ranges[pos].clone();
+ if range.start >= index.end {
+ self.ranges.insert(pos, (range_pos..index.end, default));
+ pos += 1;
+ break;
+ }
+ if range.start > range_pos {
+ self.ranges.insert(pos, (range_pos..range.start, default));
+ pos += 1;
+ range_pos = range.start;
+ }
+ if range.end >= index.end {
+ if range.end != index.end {
+ self.ranges[pos].0.start = index.end;
+ self.ranges.insert(pos, (range_pos..index.end, value));
+ }
+ pos += 1;
+ break;
+ }
+ pos += 1;
+ range_pos = range.end;
+ if pos == self.ranges.len() {
+ self.ranges.push((range_pos..index.end, default));
+ pos += 1;
+ break;
+ }
+ }
+
+ &mut self.ranges[start_pos..pos]
+ }
+
+ /// Helper method for isolation that checks the sanity of the results.
+ #[cfg(test)]
+ pub fn sanely_isolated(&self, index: Range<I>, default: T) -> Vec<(Range<I>, T)> {
+ let mut clone = self.clone();
+ let result = clone.isolate(&index, default).to_vec();
+ clone.check_sanity();
+ result
+ }
+
+ /// Produce an iterator that merges two instances together.
+ ///
+ /// Each range in the returned iterator is a subset of a range in either
+ /// `self` or `other`, and the value returned as a `Range` from `self` to `other`.
+ pub fn merge<'a>(&'a self, other: &'a Self, base: I) -> Merge<'a, I, T> {
+ Merge {
+ base,
+ sa: self.ranges.iter().peekable(),
+ sb: other.ranges.iter().peekable(),
+ }
+ }
+}
+
+/// A custom iterator that goes through two `RangedStates` and process a merge.
+#[derive(Debug)]
+pub struct Merge<'a, I, T> {
+ base: I,
+ sa: iter::Peekable<Iter<'a, (Range<I>, T)>>,
+ sb: iter::Peekable<Iter<'a, (Range<I>, T)>>,
+}
+
+impl<'a, I: Copy + Debug + Ord, T: Copy + Debug> Iterator for Merge<'a, I, T> {
+ type Item = (Range<I>, Range<Option<T>>);
+ fn next(&mut self) -> Option<Self::Item> {
+ match (self.sa.peek(), self.sb.peek()) {
+ // we have both streams
+ (Some(&(ref ra, va)), Some(&(ref rb, vb))) => {
+ let (range, usage) = if ra.start < self.base {
+ // in the middle of the left stream
+ if self.base == rb.start {
+ // right stream is starting
+ debug_assert!(self.base < ra.end);
+ (self.base..ra.end.min(rb.end), Some(*va)..Some(*vb))
+ } else {
+ // right hasn't started yet
+ debug_assert!(self.base < rb.start);
+ (self.base..rb.start, Some(*va)..None)
+ }
+ } else if rb.start < self.base {
+ // in the middle of the right stream
+ if self.base == ra.start {
+ // left stream is starting
+ debug_assert!(self.base < rb.end);
+ (self.base..ra.end.min(rb.end), Some(*va)..Some(*vb))
+ } else {
+ // left hasn't started yet
+ debug_assert!(self.base < ra.start);
+ (self.base..ra.start, None..Some(*vb))
+ }
+ } else {
+ // no active streams
+ match ra.start.cmp(&rb.start) {
+ // both are starting
+ Ordering::Equal => (ra.start..ra.end.min(rb.end), Some(*va)..Some(*vb)),
+ // only left is starting
+ Ordering::Less => (ra.start..rb.start.min(ra.end), Some(*va)..None),
+ // only right is starting
+ Ordering::Greater => (rb.start..ra.start.min(rb.end), None..Some(*vb)),
+ }
+ };
+ self.base = range.end;
+ if ra.end == range.end {
+ let _ = self.sa.next();
+ }
+ if rb.end == range.end {
+ let _ = self.sb.next();
+ }
+ Some((range, usage))
+ }
+ // only right stream
+ (None, Some(&(ref rb, vb))) => {
+ let range = self.base.max(rb.start)..rb.end;
+ self.base = rb.end;
+ let _ = self.sb.next();
+ Some((range, None..Some(*vb)))
+ }
+ // only left stream
+ (Some(&(ref ra, va)), None) => {
+ let range = self.base.max(ra.start)..ra.end;
+ self.base = ra.end;
+ let _ = self.sa.next();
+ Some((range, Some(*va)..None))
+ }
+ // done
+ (None, None) => None,
+ }
+ }
+}
+
+#[cfg(test)]
+mod test {
+ //TODO: randomized/fuzzy testing
+ use super::RangedStates;
+ use std::{fmt::Debug, ops::Range};
+
+ fn easy_merge<T: PartialEq + Copy + Debug>(
+ ra: &[(Range<usize>, T)],
+ rb: &[(Range<usize>, T)],
+ ) -> Vec<(Range<usize>, Range<Option<T>>)> {
+ RangedStates::from_slice(ra)
+ .merge(&RangedStates::from_slice(rb), 0)
+ .collect()
+ }
+
+ #[test]
+ fn sane_good() {
+ let rs = RangedStates::from_slice(&[(1..4, 9u8), (4..5, 9)]);
+ rs.check_sanity();
+ }
+
+ #[test]
+ #[should_panic]
+ fn sane_empty() {
+ let rs = RangedStates::from_slice(&[(1..4, 9u8), (5..5, 9)]);
+ rs.check_sanity();
+ }
+
+ #[test]
+ #[should_panic]
+ fn sane_intersect() {
+ let rs = RangedStates::from_slice(&[(1..4, 9u8), (3..5, 9)]);
+ rs.check_sanity();
+ }
+
+ #[test]
+ fn coalesce() {
+ let mut rs = RangedStates::from_slice(&[(1..4, 9u8), (4..5, 9), (5..7, 1), (8..9, 1)]);
+ rs.coalesce();
+ rs.check_sanity();
+ assert_eq!(rs.ranges.as_slice(), &[(1..5, 9), (5..7, 1), (8..9, 1),]);
+ }
+
+ #[test]
+ fn query() {
+ let rs = RangedStates::from_slice(&[(1..4, 1u8), (5..7, 2)]);
+ assert_eq!(rs.query(&(0..1), |v| *v), None);
+ assert_eq!(rs.query(&(1..3), |v| *v), Some(Ok(1)));
+ assert_eq!(rs.query(&(1..6), |v| *v), Some(Err(())));
+ }
+
+ #[test]
+ fn isolate() {
+ let rs = RangedStates::from_slice(&[(1..4, 9u8), (4..5, 9), (5..7, 1), (8..9, 1)]);
+ assert_eq!(&rs.sanely_isolated(4..5, 0), &[(4..5, 9u8),]);
+ assert_eq!(
+ &rs.sanely_isolated(0..6, 0),
+ &[(0..1, 0), (1..4, 9u8), (4..5, 9), (5..6, 1),]
+ );
+ assert_eq!(&rs.sanely_isolated(8..10, 1), &[(8..9, 1), (9..10, 1),]);
+ assert_eq!(
+ &rs.sanely_isolated(6..9, 0),
+ &[(6..7, 1), (7..8, 0), (8..9, 1),]
+ );
+ }
+
+ #[test]
+ fn merge_same() {
+ assert_eq!(
+ &easy_merge(&[(1..4, 0u8),], &[(1..4, 2u8),],),
+ &[(1..4, Some(0)..Some(2)),]
+ );
+ }
+
+ #[test]
+ fn merge_empty() {
+ assert_eq!(
+ &easy_merge(&[(1..2, 0u8),], &[],),
+ &[(1..2, Some(0)..None),]
+ );
+ assert_eq!(
+ &easy_merge(&[], &[(3..4, 1u8),],),
+ &[(3..4, None..Some(1)),]
+ );
+ }
+
+ #[test]
+ fn merge_separate() {
+ assert_eq!(
+ &easy_merge(&[(1..2, 0u8), (5..6, 1u8),], &[(2..4, 2u8),],),
+ &[
+ (1..2, Some(0)..None),
+ (2..4, None..Some(2)),
+ (5..6, Some(1)..None),
+ ]
+ );
+ }
+
+ #[test]
+ fn merge_subset() {
+ assert_eq!(
+ &easy_merge(&[(1..6, 0u8),], &[(2..4, 2u8),],),
+ &[
+ (1..2, Some(0)..None),
+ (2..4, Some(0)..Some(2)),
+ (4..6, Some(0)..None),
+ ]
+ );
+ assert_eq!(
+ &easy_merge(&[(2..4, 0u8),], &[(1..4, 2u8),],),
+ &[(1..2, None..Some(2)), (2..4, Some(0)..Some(2)),]
+ );
+ }
+
+ #[test]
+ fn merge_all() {
+ assert_eq!(
+ &easy_merge(&[(1..4, 0u8), (5..8, 1u8),], &[(2..6, 2u8), (7..9, 3u8),],),
+ &[
+ (1..2, Some(0)..None),
+ (2..4, Some(0)..Some(2)),
+ (4..5, None..Some(2)),
+ (5..6, Some(1)..Some(2)),
+ (6..7, Some(1)..None),
+ (7..8, Some(1)..Some(3)),
+ (8..9, None..Some(3)),
+ ]
+ );
+ }
+}
diff --git a/gfx/wgpu/wgpu-core/src/track/texture.rs b/gfx/wgpu/wgpu-core/src/track/texture.rs
new file mode 100644
index 0000000000..6d1d4a5935
--- /dev/null
+++ b/gfx/wgpu/wgpu-core/src/track/texture.rs
@@ -0,0 +1,466 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use super::{range::RangedStates, PendingTransition, ResourceState, Unit};
+use crate::{
+ device::MAX_MIP_LEVELS,
+ id::{TextureId, Valid},
+ resource::TextureUse,
+};
+
+use arrayvec::ArrayVec;
+
+use std::{iter, ops::Range};
+
+//TODO: store `hal::image::State` here to avoid extra conversions
+type PlaneStates = RangedStates<hal::image::Layer, Unit<TextureUse>>;
+
+#[derive(Clone, Debug, PartialEq, Eq)]
+pub struct TextureSelector {
+ //pub aspects: hal::format::Aspects,
+ pub levels: Range<hal::image::Level>,
+ pub layers: Range<hal::image::Layer>,
+}
+
+#[derive(Clone, Debug, Default, PartialEq)]
+pub(crate) struct TextureState {
+ mips: ArrayVec<[PlaneStates; MAX_MIP_LEVELS as usize]>,
+ /// True if we have the information about all the subresources here
+ full: bool,
+}
+
+impl PendingTransition<TextureState> {
+ fn collapse(self) -> Result<TextureUse, Self> {
+ if self.usage.start.is_empty()
+ || self.usage.start == self.usage.end
+ || !TextureUse::WRITE_ALL.intersects(self.usage.start | self.usage.end)
+ {
+ Ok(self.usage.start | self.usage.end)
+ } else {
+ Err(self)
+ }
+ }
+}
+
+impl TextureState {
+ pub fn new(mip_level_count: hal::image::Level, array_layer_count: hal::image::Layer) -> Self {
+ Self {
+ mips: iter::repeat_with(|| {
+ PlaneStates::from_range(0..array_layer_count, Unit::new(TextureUse::UNINITIALIZED))
+ })
+ .take(mip_level_count as usize)
+ .collect(),
+ full: true,
+ }
+ }
+}
+
+impl ResourceState for TextureState {
+ type Id = TextureId;
+ type Selector = TextureSelector;
+ type Usage = TextureUse;
+
+ fn query(&self, selector: Self::Selector) -> Option<Self::Usage> {
+ let mut result = None;
+ // Note: we only consider the subresources tracked by `self`.
+ // If some are not known to `self`, it means the can assume the
+ // initial state to whatever we need, which we can always make
+ // to be the same as the query result for the known subresources.
+ let num_levels = self.mips.len();
+ if self.full {
+ assert!(num_levels >= selector.levels.end as usize);
+ }
+ let mip_start = num_levels.min(selector.levels.start as usize);
+ let mip_end = num_levels.min(selector.levels.end as usize);
+ for mip in self.mips[mip_start..mip_end].iter() {
+ match mip.query(&selector.layers, |unit| unit.last) {
+ None => {}
+ Some(Ok(usage)) if result == Some(usage) => {}
+ Some(Ok(usage)) if result.is_none() => {
+ result = Some(usage);
+ }
+ Some(Ok(_)) | Some(Err(())) => return None,
+ }
+ }
+ result
+ }
+
+ fn change(
+ &mut self,
+ id: Valid<Self::Id>,
+ selector: Self::Selector,
+ usage: Self::Usage,
+ mut output: Option<&mut Vec<PendingTransition<Self>>>,
+ ) -> Result<(), PendingTransition<Self>> {
+ if self.full {
+ assert!(self.mips.len() >= selector.levels.end as usize);
+ } else {
+ while self.mips.len() < selector.levels.end as usize {
+ self.mips.push(PlaneStates::empty());
+ }
+ }
+ for (mip_id, mip) in self.mips[selector.levels.start as usize..selector.levels.end as usize]
+ .iter_mut()
+ .enumerate()
+ {
+ let level = selector.levels.start + mip_id as hal::image::Level;
+ let layers = mip.isolate(&selector.layers, Unit::new(usage));
+ for &mut (ref range, ref mut unit) in layers {
+ if unit.last == usage && TextureUse::ORDERED.contains(usage) {
+ continue;
+ }
+ // TODO: Can't satisfy clippy here unless we modify
+ // `TextureSelector` to use `std::ops::RangeBounds`.
+ #[allow(clippy::range_plus_one)]
+ let pending = PendingTransition {
+ id,
+ selector: TextureSelector {
+ levels: level..level + 1,
+ layers: range.clone(),
+ },
+ usage: unit.last..usage,
+ };
+
+ *unit = match output {
+ None => {
+ assert_eq!(
+ unit.first, None,
+ "extending a state that is already a transition"
+ );
+ Unit::new(pending.collapse()?)
+ }
+ Some(ref mut out) => {
+ out.push(pending);
+ Unit {
+ first: unit.first.or(Some(unit.last)),
+ last: usage,
+ }
+ }
+ };
+ }
+ }
+ Ok(())
+ }
+
+ fn prepend(
+ &mut self,
+ id: Valid<Self::Id>,
+ selector: Self::Selector,
+ usage: Self::Usage,
+ ) -> Result<(), PendingTransition<Self>> {
+ assert!(self.mips.len() >= selector.levels.end as usize);
+ for (mip_id, mip) in self.mips[selector.levels.start as usize..selector.levels.end as usize]
+ .iter_mut()
+ .enumerate()
+ {
+ let level = selector.levels.start + mip_id as hal::image::Level;
+ let layers = mip.isolate(&selector.layers, Unit::new(usage));
+ for &mut (ref range, ref mut unit) in layers {
+ match unit.first {
+ Some(old) if old != usage => {
+ return Err(PendingTransition {
+ id,
+ selector: TextureSelector {
+ levels: level..level + 1,
+ layers: range.clone(),
+ },
+ usage: old..usage,
+ });
+ }
+ _ => {
+ unit.first = Some(usage);
+ }
+ }
+ }
+ }
+ Ok(())
+ }
+
+ fn merge(
+ &mut self,
+ id: Valid<Self::Id>,
+ other: &Self,
+ mut output: Option<&mut Vec<PendingTransition<Self>>>,
+ ) -> Result<(), PendingTransition<Self>> {
+ let mut temp = Vec::new();
+ if self.full {
+ assert!(self.mips.len() >= other.mips.len());
+ } else {
+ while self.mips.len() < other.mips.len() {
+ self.mips.push(PlaneStates::empty());
+ }
+ }
+
+ for (mip_id, (mip_self, mip_other)) in self.mips.iter_mut().zip(&other.mips).enumerate() {
+ let level = mip_id as hal::image::Level;
+ temp.extend(mip_self.merge(mip_other, 0));
+ mip_self.clear();
+
+ for (layers, states) in temp.drain(..) {
+ let unit = match states {
+ Range {
+ start: None,
+ end: None,
+ } => unreachable!(),
+ Range {
+ start: Some(start),
+ end: None,
+ } => start,
+ Range {
+ start: None,
+ end: Some(end),
+ } => end,
+ Range {
+ start: Some(start),
+ end: Some(end),
+ } => {
+ let to_usage = end.port();
+ if start.last == to_usage && TextureUse::ORDERED.contains(to_usage) {
+ Unit {
+ first: match output {
+ None => start.first,
+ Some(_) => start.first.or(Some(start.last)),
+ },
+ last: end.last,
+ }
+ } else {
+ // TODO: Can't satisfy clippy here unless we modify
+ // `TextureSelector` to use `std::ops::RangeBounds`.
+ #[allow(clippy::range_plus_one)]
+ let pending = PendingTransition {
+ id,
+ selector: TextureSelector {
+ levels: level..level + 1,
+ layers: layers.clone(),
+ },
+ usage: start.last..to_usage,
+ };
+
+ match output {
+ None => {
+ assert_eq!(
+ start.first, None,
+ "extending a state that is already a transition"
+ );
+ Unit::new(pending.collapse()?)
+ }
+ Some(ref mut out) => {
+ out.push(pending);
+ Unit {
+ // this has to leave a valid `first` state
+ first: start.first.or(Some(start.last)),
+ last: end.last,
+ }
+ }
+ }
+ }
+ }
+ };
+ mip_self.append(layers, unit);
+ }
+ }
+
+ Ok(())
+ }
+
+ fn optimize(&mut self) {
+ for mip in self.mips.iter_mut() {
+ mip.coalesce();
+ }
+ }
+}
+
+#[cfg(test)]
+mod test {
+ //TODO: change() tests
+ use super::*;
+ use crate::id::Id;
+
+ #[test]
+ fn query() {
+ let mut ts = TextureState::default();
+ ts.mips.push(PlaneStates::empty());
+ ts.mips.push(PlaneStates::from_slice(&[
+ (1..3, Unit::new(TextureUse::SAMPLED)),
+ (3..5, Unit::new(TextureUse::SAMPLED)),
+ (5..6, Unit::new(TextureUse::STORAGE_LOAD)),
+ ]));
+
+ assert_eq!(
+ ts.query(TextureSelector {
+ levels: 1..2,
+ layers: 2..5,
+ }),
+ // level 1 matches
+ Some(TextureUse::SAMPLED),
+ );
+ assert_eq!(
+ ts.query(TextureSelector {
+ levels: 0..2,
+ layers: 2..5,
+ }),
+ // level 0 is empty, level 1 matches
+ Some(TextureUse::SAMPLED),
+ );
+ assert_eq!(
+ ts.query(TextureSelector {
+ levels: 1..2,
+ layers: 1..5,
+ }),
+ // level 1 matches with gaps
+ Some(TextureUse::SAMPLED),
+ );
+ assert_eq!(
+ ts.query(TextureSelector {
+ levels: 1..2,
+ layers: 4..6,
+ }),
+ // level 1 doesn't match
+ None,
+ );
+ }
+
+ #[test]
+ fn merge() {
+ let id = Id::dummy();
+ let mut ts1 = TextureState::default();
+ ts1.mips.push(PlaneStates::from_slice(&[(
+ 1..3,
+ Unit::new(TextureUse::SAMPLED),
+ )]));
+ let mut ts2 = TextureState::default();
+ assert_eq!(
+ ts1.merge(id, &ts2, None),
+ Ok(()),
+ "failed to merge with an empty"
+ );
+
+ ts2.mips.push(PlaneStates::from_slice(&[(
+ 1..2,
+ Unit::new(TextureUse::COPY_SRC),
+ )]));
+ assert_eq!(
+ ts1.merge(Id::dummy(), &ts2, None),
+ Ok(()),
+ "failed to extend a compatible state"
+ );
+ assert_eq!(
+ ts1.mips[0].query(&(1..2), |&v| v),
+ Some(Ok(Unit {
+ first: None,
+ last: TextureUse::SAMPLED | TextureUse::COPY_SRC,
+ })),
+ "wrong extension result"
+ );
+
+ ts2.mips[0] = PlaneStates::from_slice(&[(1..2, Unit::new(TextureUse::COPY_DST))]);
+ assert_eq!(
+ ts1.clone().merge(Id::dummy(), &ts2, None),
+ Err(PendingTransition {
+ id,
+ selector: TextureSelector {
+ levels: 0..1,
+ layers: 1..2,
+ },
+ usage: TextureUse::SAMPLED | TextureUse::COPY_SRC..TextureUse::COPY_DST,
+ }),
+ "wrong error on extending with incompatible state"
+ );
+
+ let mut list = Vec::new();
+ ts2.mips[0] = PlaneStates::from_slice(&[
+ (1..2, Unit::new(TextureUse::COPY_DST)),
+ (
+ 2..3,
+ Unit {
+ first: Some(TextureUse::COPY_SRC),
+ last: TextureUse::ATTACHMENT_WRITE,
+ },
+ ),
+ ]);
+ ts1.merge(Id::dummy(), &ts2, Some(&mut list)).unwrap();
+ assert_eq!(
+ &list,
+ &[
+ PendingTransition {
+ id,
+ selector: TextureSelector {
+ levels: 0..1,
+ layers: 1..2,
+ },
+ usage: TextureUse::SAMPLED | TextureUse::COPY_SRC..TextureUse::COPY_DST,
+ },
+ PendingTransition {
+ id,
+ selector: TextureSelector {
+ levels: 0..1,
+ layers: 2..3,
+ },
+ // the transition links the end of the base rage (..SAMPLED)
+ // with the start of the next range (COPY_SRC..)
+ usage: TextureUse::SAMPLED..TextureUse::COPY_SRC,
+ },
+ ],
+ "replacing produced wrong transitions"
+ );
+ assert_eq!(
+ ts1.mips[0].query(&(1..2), |&v| v),
+ Some(Ok(Unit {
+ first: Some(TextureUse::SAMPLED | TextureUse::COPY_SRC),
+ last: TextureUse::COPY_DST,
+ })),
+ "wrong final layer 1 state"
+ );
+ assert_eq!(
+ ts1.mips[0].query(&(2..3), |&v| v),
+ Some(Ok(Unit {
+ first: Some(TextureUse::SAMPLED),
+ last: TextureUse::ATTACHMENT_WRITE,
+ })),
+ "wrong final layer 2 state"
+ );
+
+ list.clear();
+ ts2.mips[0] = PlaneStates::from_slice(&[(
+ 2..3,
+ Unit {
+ first: Some(TextureUse::ATTACHMENT_WRITE),
+ last: TextureUse::COPY_SRC,
+ },
+ )]);
+ ts1.merge(Id::dummy(), &ts2, Some(&mut list)).unwrap();
+ assert_eq!(&list, &[], "unexpected replacing transition");
+
+ list.clear();
+ ts2.mips[0] = PlaneStates::from_slice(&[(
+ 2..3,
+ Unit {
+ first: Some(TextureUse::COPY_DST),
+ last: TextureUse::COPY_DST,
+ },
+ )]);
+ ts1.merge(Id::dummy(), &ts2, Some(&mut list)).unwrap();
+ assert_eq!(
+ &list,
+ &[PendingTransition {
+ id,
+ selector: TextureSelector {
+ levels: 0..1,
+ layers: 2..3,
+ },
+ usage: TextureUse::COPY_SRC..TextureUse::COPY_DST,
+ },],
+ "invalid replacing transition"
+ );
+ assert_eq!(
+ ts1.mips[0].query(&(2..3), |&v| v),
+ Some(Ok(Unit {
+ // the initial state here is never expected to change
+ first: Some(TextureUse::SAMPLED),
+ last: TextureUse::COPY_DST,
+ })),
+ "wrong final layer 2 state"
+ );
+ }
+}