diff options
Diffstat (limited to 'gfx/wgpu/wgpu-core/src/track')
-rw-r--r-- | gfx/wgpu/wgpu-core/src/track/buffer.rs | 241 | ||||
-rw-r--r-- | gfx/wgpu/wgpu-core/src/track/mod.rs | 593 | ||||
-rw-r--r-- | gfx/wgpu/wgpu-core/src/track/range.rs | 399 | ||||
-rw-r--r-- | gfx/wgpu/wgpu-core/src/track/texture.rs | 466 |
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" + ); + } +} |