From 65192417a36966ece8ae5687864f71894692e78b Mon Sep 17 00:00:00 2001 From: Connor Fitzgerald Date: Mon, 16 May 2022 23:08:27 -0400 Subject: [PATCH] temp28 - document tracker and usages --- wgpu-core/src/track/mod.rs | 85 +++++++++++++++ wgpu-core/src/track/texture.rs | 185 --------------------------------- wgpu-hal/src/lib.rs | 68 ++++++++---- 3 files changed, 132 insertions(+), 206 deletions(-) diff --git a/wgpu-core/src/track/mod.rs b/wgpu-core/src/track/mod.rs index 97257f985e..0e2834e496 100644 --- a/wgpu-core/src/track/mod.rs +++ b/wgpu-core/src/track/mod.rs @@ -1,3 +1,88 @@ +/*! Resource State and Lifetime Trackers + * + * These structures are responsible for keeping track of resource state, + * generating barriers where needed, and making sure resources are kept + * alive until the trackers die. + * + * ## General Architecture + * + * Tracking is some of the hottest code in the entire codebase, so the trackers + * are designed to be as cache efficient as possible. They store resource state + * in flat vectors, storing metadata SOA style, one vector per type of metadata. + * + * In wgpu, resource IDs are allocated and re-used, so will always be as low + * as reasonably possible. This allows us to use the ID as an index into an array. + * + * ## Statefulness + * + * There are two main types of trackers, stateful and stateless. + * + * Stateful trackers are for buffers and textures. They both have + * resource state attached to them which needs to be used to generate + * automatic synchronization. Because of the different requirements of + * buffers and textures, they have two separate tracking structures. + * + * Stateless trackers only store metadata and own the given resource. + * + * ## Use Case + * + * Within each type of tracker, the trackers are further split into 3 different + * use cases, Bind Group, Usage Scope, and a full Tracker. + * + * Bind Group trackers are just a list of different resources, their refcount, + * and how they are used. Textures are used via a selector and a usage type. + * Buffers by just a usage type. Stateless resources don't have a usage type. + * + * Usage Scope trackers are only for stateful resources. These trackers represent + * a single [`UsageScope`] in the spec. When a use is added to a usage scope, + * it is merged with all other uses of that resource in that scope. If there + * is a usage conflict, merging will fail and an error will be reported. + * + * Full trackers represent a before and after state of a resource. These + * are used for tracking on the device and on command buffers. The before + * state represents the state the resource is first used as in the command buffer, + * the after state is the state the command buffer leaves the resource in. + * These double ended buffers can then be used to generate the needed transitions + * between command buffers. + * + * ## Dense Datastructure with Sparse Data + * + * This tracking system is based on having completely dense data, but trackers do + * not always contain every resource. Some resources (or even most resources) go + * unused in any given command buffer. So to help speed up the process of iterating + * through possibly thousands of resources, we use a bit vector to represent if + * a resource is in the buffer or not. This allows us extremely efficient memory + * utilization, as well as being able to bail out of whole blocks of 32-64 resources + * with a single usize comparison with zero. In practice this means that merging + * partially resident buffers is extremely quick. + * + * The main advantage of this dense datastructure is that we can do merging + * of trackers in an extremely efficient fashion that results in us doing linear + * scans down a couple of buffers. CPUs and their caches absolutely eat this up. + * + * ## Stateful Resource Operations + * + * All operations on stateful trackers boil down to one of four operations: + * - `insert(tracker, new_state)` adds a resource with a given state to the tracker + * for the first time. + * - `merge(tracker, new_state)` merges this new state with the previous state, checking + * for usage conflicts. + * - `barrier(tracker, new_state)` compares the given state to the existing state and + * generates the needed barriers. + * - `update(tracker, new_state)` takes the given new state and overrides the old state. + * + * This allows us to compose the operations to form the various kinds of tracker merges + * that need to happen in the codebase. For each resource in the given merger, the following + * operation applies: + * + * UsageScope <- Resource = insert(scope, usage) OR merge(scope, usage) + * UsageScope <- UsageScope = insert(scope, scope) OR merge(scope, scope) + * CommandBuffer <- UsageScope = insert(buffer.start, buffer.end, scope) OR barrier(buffer.end, scope) + update(buffer.end, scope) + * Deivce <- CommandBuffer = insert(device.start, device.end, buffer.start, buffer.end) OR barrier(device.end, buffer.start) + update(device.end, buffer.end) + * + * [`UsageScope`]: https://gpuweb.github.io/gpuweb/#programming-model-synchronization +!*/ + mod buffer; mod range; mod stateless; diff --git a/wgpu-core/src/track/texture.rs b/wgpu-core/src/track/texture.rs index f4e5fe0246..7a290fc47e 100644 --- a/wgpu-core/src/track/texture.rs +++ b/wgpu-core/src/track/texture.rs @@ -1225,188 +1225,3 @@ unsafe fn update( } } } - -// #[cfg(test)] -// mod test { -// use super::*; - -// struct TrackingTest { -// life_guard: LifeGuard, -// selector: TextureSelector, -// set: TextureStateSet, -// metadata: ResourceMetadata, -// } - -// fn setup(mips: Range, layers: Range, states: &[TextureUses]) -> TrackingTest { -// let life_guard = LifeGuard::new("test"); -// let selector = TextureSelector { mips, layers }; - -// let mut set = TextureStateSet::new(); -// let mut metadata = ResourceMetadata::::new(); - -// set.set_size(states.len()); -// metadata.set_size(states.len()); - -// for &state in states { -// set.simple[0] = state; - -// metadata.owned.set(0, true); -// metadata.ref_counts[0] = None; // Okay because we only read this through metadata provider -// metadata.epochs[0] = 1; -// } - -// TrackingTest { -// life_guard, -// selector, -// set, -// metadata, -// } -// } - -// #[test] -// fn simple_transition() { -// let mut test = setup(0..1, 0..1, &[TextureUses::UNINITIALIZED]); - -// let mut barriers = Vec::new(); -// unsafe { -// let _ = state_combine( -// Some((&test.life_guard, &test.selector)), -// None, -// &mut test.set, -// &mut test.metadata, -// 0, -// 0, -// LayeredStateProvider::KnownSingle { -// state: TextureUses::RESOURCE, -// }, -// ResourceMetadataProvider::Direct { -// epoch: 1, -// ref_count: Cow::Borrowed(test.life_guard.ref_count.as_ref().unwrap()), -// }, -// Some(&mut barriers), -// ); -// }; -// assert_eq!(test.set.simple, &[TextureUses::RESOURCE]); -// assert_eq!( -// barriers, -// &[PendingTransition { -// id: 0, -// selector: test.selector, -// usage: TextureUses::UNINITIALIZED..TextureUses::RESOURCE -// }] -// ); -// } - -// #[test] -// fn simple_merger() { -// let mut test = setup(0..1, 0..1, &[TextureUses::COPY_SRC]); - -// unsafe { -// let res = state_combine( -// Some((&test.life_guard, &test.selector)), -// None, -// &mut test.set, -// &mut test.metadata, -// 0, -// 0, -// LayeredStateProvider::KnownSingle { -// state: TextureUses::RESOURCE, -// }, -// ResourceMetadataProvider::Direct { -// epoch: 1, -// ref_count: Cow::Borrowed(test.life_guard.ref_count.as_ref().unwrap()), -// }, -// None, -// ); -// assert_eq!(res, Ok(())); -// }; -// assert_eq!( -// test.set.simple, -// &[TextureUses::COPY_SRC | TextureUses::RESOURCE] -// ); -// } - -// #[test] -// fn simple_to_complex_transition() { -// let mut test = setup(0..1, 0..2, &[TextureUses::RESOURCE]); - -// let transition_selector = TextureSelector { -// mips: 0..1, -// layers: 1..2, -// }; - -// let mut barriers = Vec::new(); -// unsafe { -// let _ = state_combine( -// Some((&test.life_guard, &test.selector)), -// None, -// &mut test.set, -// &mut test.metadata, -// 0, -// 0, -// LayeredStateProvider::Selector { -// selector: transition_selector.clone(), -// state: TextureUses::STORAGE_WRITE, -// }, -// ResourceMetadataProvider::Direct { -// epoch: 1, -// ref_count: Cow::Borrowed(test.life_guard.ref_count.as_ref().unwrap()), -// }, -// Some(&mut barriers), -// ); -// }; -// assert_eq!(test.set.simple, &[TextureUses::COMPLEX]); -// assert_eq!( -// barriers, -// &[PendingTransition { -// id: 0, -// selector: transition_selector, -// usage: TextureUses::RESOURCE..TextureUses::STORAGE_WRITE -// }] -// ); -// } - -// #[test] -// fn simple_to_complex_merger() { -// let mut test = setup(0..1, 0..2, &[TextureUses::RESOURCE]); - -// let transition_selector = TextureSelector { -// mips: 0..1, -// layers: 1..2, -// }; - -// unsafe { -// let res = state_combine( -// Some((&test.life_guard, &test.selector)), -// None, -// &mut test.set, -// &mut test.metadata, -// 0, -// 0, -// LayeredStateProvider::Selector { -// selector: transition_selector.clone(), -// state: TextureUses::DEPTH_STENCIL_READ, -// }, -// ResourceMetadataProvider::Direct { -// epoch: 1, -// ref_count: Cow::Borrowed(test.life_guard.ref_count.as_ref().unwrap()), -// }, -// None, -// ); -// assert_eq!(res, Ok(())); -// }; -// assert_eq!(test.set.simple, &[TextureUses::COMPLEX]); -// let complex = test.set.complex.get(&0).unwrap(); -// let mip0: Vec<_> = complex.mips[0].iter().cloned().collect(); -// assert_eq!( -// mip0, -// &[ -// (0..1, TextureUses::RESOURCE), -// ( -// 1..2, -// TextureUses::RESOURCE | TextureUses::DEPTH_STENCIL_READ -// ) -// ] -// ); -// } -// } diff --git a/wgpu-hal/src/lib.rs b/wgpu-hal/src/lib.rs index d8752e6a10..05ecd8d60b 100644 --- a/wgpu-hal/src/lib.rs +++ b/wgpu-hal/src/lib.rs @@ -627,26 +627,39 @@ bitflags!( bitflags::bitflags! { /// Similar to `wgt::BufferUsages` but for internal use. pub struct BufferUses: u16 { + /// The argument to a read-only mapping. const MAP_READ = 1 << 0; + /// The argument to a write-only mapping. const MAP_WRITE = 1 << 1; + /// The source of a hardware copy. const COPY_SRC = 1 << 2; + /// The destination of a hardware copy. const COPY_DST = 1 << 3; + /// The index buffer used for drawing. const INDEX = 1 << 4; + /// A vertex buffer used for drawing. const VERTEX = 1 << 5; + /// A uniform buffer bound in a bind group. const UNIFORM = 1 << 6; + /// A read-only storage buffer used in a bind group. + /// + /// Can be combined with STORAGE_WRITE to create a read-write usage. const STORAGE_READ = 1 << 7; + /// A read-write or write only buffer used for binding + /// + /// Can be combined with STORAGE_READ to create a read-write usage. const STORAGE_WRITE = 1 << 8; + /// The indirect or count buffer in a indirect draw or dispatch. const INDIRECT = 1 << 9; - /// The combination of usages that can be used together (read-only). + /// The combination of states that a buffer may be in _at the same time_. const INCLUSIVE = Self::MAP_READ.bits | Self::COPY_SRC.bits | Self::INDEX.bits | Self::VERTEX.bits | Self::UNIFORM.bits | Self::STORAGE_READ.bits | Self::INDIRECT.bits; - /// The combination of exclusive usages (write-only and read-write). - /// These usages may still show up with others, but can't automatically be combined. + /// The combination of states that a buffer must exclusively be in. const EXCLUSIVE = Self::MAP_WRITE.bits | Self::COPY_DST.bits | Self::STORAGE_WRITE.bits; /// The combination of all usages that the are guaranteed to be be ordered by the hardware. - /// If a usage is not ordered, then even if it doesn't change between draw calls, there - /// still need to be pipeline barriers inserted for synchronization. + /// If a usage is ordered, then if the buffer state doesn't change between draw calls, there + /// are no barriers needed for synchronization. const ORDERED = Self::INCLUSIVE.bits | Self::MAP_WRITE.bits; } } @@ -654,26 +667,39 @@ bitflags::bitflags! { bitflags::bitflags! { /// Similar to `wgt::TextureUsages` but for internal use. pub struct TextureUses: u16 { - const PRESENT = 1 << 0; - const COPY_SRC = 1 << 1; - const COPY_DST = 1 << 2; - const RESOURCE = 1 << 3; - const COLOR_TARGET = 1 << 4; - const DEPTH_STENCIL_READ = 1 << 5; - const DEPTH_STENCIL_WRITE = 1 << 6; - const STORAGE_READ = 1 << 7; - const STORAGE_WRITE = 1 << 8; - /// The combination of usages that can be used together (read-only). + /// The texture is in unknown state. + const UNINITIALIZED = 1 << 0; + /// Ready to present image to the surface. + const PRESENT = 1 << 1; + /// The source of a hardware copy. + const COPY_SRC = 1 << 2; + /// The destination of a hardware copy. + const COPY_DST = 1 << 3; + /// Read-only sampled or fetched resource. + const RESOURCE = 1 << 4; + /// The color target of a renderpass. + const COLOR_TARGET = 1 << 5; + /// Read-only depth stencil usage. + const DEPTH_STENCIL_READ = 1 << 6; + /// Read-write depth stencil usage + const DEPTH_STENCIL_WRITE = 1 << 7; + /// Read-only storage buffer usage. Corresponds to a UAV in d3d, so is exclusive, despite being read only. + const STORAGE_READ = 1 << 8; + /// Read-write storage buffer usage. + const STORAGE_WRITE = 1 << 9; + /// The combination of states that a texture may be in _at the same time_. const INCLUSIVE = Self::COPY_SRC.bits | Self::RESOURCE.bits | Self::DEPTH_STENCIL_READ.bits; - /// The combination of exclusive usages (write-only and read-write). - /// These usages may still show up with others, but can't automatically be combined. + /// The combination of states that a texture must exclusively be in. const EXCLUSIVE = Self::COPY_DST.bits | Self::COLOR_TARGET.bits | Self::DEPTH_STENCIL_WRITE.bits | Self::STORAGE_READ.bits | Self::STORAGE_WRITE.bits | Self::PRESENT.bits; /// The combination of all usages that the are guaranteed to be be ordered by the hardware. - /// If a usage is not ordered, then even if it doesn't change between draw calls, there - /// still need to be pipeline barriers inserted for synchronization. + /// If a usage is ordered, then if the texture state doesn't change between draw calls, there + /// are no barriers needed for synchronization. const ORDERED = Self::INCLUSIVE.bits | Self::COLOR_TARGET.bits | Self::DEPTH_STENCIL_WRITE.bits | Self::STORAGE_READ.bits; - const COMPLEX = 1 << 9; - const UNINITIALIZED = 1 << 10; + + /// Flag used by the wgpu-core texture tracker to say a texture is in different states for every sub-resource + const COMPLEX = 1 << 10; + /// Flag used by the wgpu-core texture tracker to say that the tracker does not know the state of the sub-resource. + /// This is different from UNINITIALIZED as that says the tracker does know, but the texture has not been initialized. const UNKNOWN = 1 << 11; } }