Name Description Size
buffer.rs ! Buffer Trackers Buffers are represented by a single state for the whole resource, a 16 bit bitflag of buffer usages. Because there is only ever one subresource, they have no selector. ! 27487
metadata.rs The `ResourceMetadata` type. 8309
mod.rs ! 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. A lot of the tracker code is deeply unsafe, using unchecked accesses all over to make performance as good as possible. However, for all unsafe accesses, there is a corresponding debug assert the checks if that access is valid. This helps get bugs caught fast, while still letting users not need to pay for the bounds checks. In wgpu, each resource ID includes a bitfield holding an index. Indices are allocated and re-used, so they will always be as low as reasonably possible. This allows us to use IDs to index into an array of tracking information. ## 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: ```text 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) Device <- 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 26338
range.rs 6682
stateless.rs ! Stateless Trackers Stateless trackers don't have any state, so make no distinction between a usage scope and a full tracker. ! 7014
texture.rs ! Texture Trackers Texture trackers are significantly more complicated than the buffer trackers because textures can be in a "complex" state where each individual subresource can potentially be in a different state from every other subtresource. These complex states are stored separately from the simple states because they are signifignatly more difficult to track and most resources spend the vast majority of their lives in simple states. There are two special texture usages: `UNKNOWN` and `UNINITIALIZED`. - `UNKNOWN` is only used in complex states and is used to signify that the complex state does not know anything about those subresources. It cannot leak into transitions, it is invalid to transition into UNKNOWN state. - `UNINITIALIZED` is used in both simple and complex states to mean the texture is known to be in some undefined state. Any transition away from UNINITIALIZED will treat the contents as junk. ! 53531