Skip to content

Commit a0dfb28

Browse files
authored
Document wgpu_core id handling, factories, etc. (#2973)
1 parent f5fa92a commit a0dfb28

File tree

2 files changed

+189
-0
lines changed

2 files changed

+189
-0
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,7 @@ the same every time it is rendered, we now warn if it is missing.
8989

9090
#### General
9191
- Add WGSL examples to complement existing examples written in GLSL by @norepimorphism in [#2888](https://github.com/gfx-rs/wgpu/pull/2888)
92+
- Document `wgpu_core` resource allocation. @jimb in [#2973](https://github.com/gfx-rs/wgpu/pull/2973)
9293

9394
### Performance
9495

wgpu-core/src/hub.rs

Lines changed: 188 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,154 @@
1+
/*! Allocating resource ids, and tracking the resources they refer to.
2+
3+
The `wgpu_core` API uses identifiers of type [`Id<R>`] to refer to
4+
resources of type `R`. For example, [`id::DeviceId`] is an alias for
5+
`Id<Device<Empty>>`, and [`id::BufferId`] is an alias for
6+
`Id<Buffer<Empty>>`. `Id` implements `Copy`, `Hash`, `Eq`, `Ord`, and
7+
of course `Debug`.
8+
9+
Each `Id` contains not only an index for the resource it denotes but
10+
also a [`Backend`] indicating which `wgpu` backend it belongs to. You
11+
can use the [`gfx_select`] macro to dynamically dispatch on an id's
12+
backend to a function specialized at compile time for a specific
13+
backend. See that macro's documentation for details.
14+
15+
`Id`s also incorporate a generation number, for additional validation.
16+
17+
The resources to which identifiers refer are freed explicitly.
18+
Attempting to use an identifier for a resource that has been freed
19+
elicits an error result.
20+
21+
## Assigning ids to resources
22+
23+
The users of `wgpu_core` generally want resource ids to be assigned
24+
in one of two ways:
25+
26+
- Users like `wgpu` want `wgpu_core` to assign ids to resources itself.
27+
For example, `wgpu` expects to call `Global::device_create_buffer`
28+
and have the return value indicate the newly created buffer's id.
29+
30+
- Users like `player` and Firefox want to allocate ids themselves, and
31+
pass `Global::device_create_buffer` and friends the id to assign the
32+
new resource.
33+
34+
To accommodate either pattern, `wgpu_core` methods that create
35+
resources all expect an `id_in` argument that the caller can use to
36+
specify the id, and they all return the id used. For example, the
37+
declaration of `Global::device_create_buffer` looks like this:
38+
39+
```ignore
40+
impl<G: GlobalIdentityHandlerFactory> Global<G> {
41+
/* ... */
42+
pub fn device_create_buffer<A: HalApi>(
43+
&self,
44+
device_id: id::DeviceId,
45+
desc: &resource::BufferDescriptor,
46+
id_in: Input<G, id::BufferId>,
47+
) -> (id::BufferId, Option<resource::CreateBufferError>) {
48+
/* ... */
49+
}
50+
/* ... */
51+
}
52+
```
53+
54+
Users that want to assign resource ids themselves pass in the id they
55+
want as the `id_in` argument, whereas users that want `wgpu_core`
56+
itself to choose ids always pass `()`. In either case, the id
57+
ultimately assigned is returned as the first element of the tuple.
58+
59+
Producing true identifiers from `id_in` values is the job of an
60+
[`IdentityHandler`] implementation, which has an associated type
61+
[`Input`] saying what type of `id_in` values it accepts, and a
62+
[`process`] method that turns such values into true identifiers of
63+
type `I`. There are two kinds of `IdentityHandler`s:
64+
65+
- Users that want `wgpu_core` to assign ids generally use
66+
[`IdentityManager`] ([wrapped in a mutex]). Its `Input` type is
67+
`()`, and it tracks assigned ids and generation numbers as
68+
necessary. (This is what `wgpu` does.)
69+
70+
- Users that want to assign ids themselves use an `IdentityHandler`
71+
whose `Input` type is `I` itself, and whose `process` method simply
72+
passes the `id_in` argument through unchanged. For example, the
73+
`player` crate uses an `IdentityPassThrough` type whose `process`
74+
method simply adjusts the id's backend (since recordings can be
75+
replayed on a different backend than the one they were created on)
76+
but passes the rest of the id's content through unchanged.
77+
78+
Because an `IdentityHandler<I>` can only create ids for a single
79+
resource type `I`, constructing a [`Global`] entails constructing a
80+
separate `IdentityHandler<I>` for each resource type `I` that the
81+
`Global` will manage: an `IdentityHandler<DeviceId>`, an
82+
`IdentityHandler<TextureId>`, and so on.
83+
84+
The [`Global::new`] function could simply take a large collection of
85+
`IdentityHandler<I>` implementations as arguments, but that would be
86+
ungainly. Instead, `Global::new` expects a `factory` argument that
87+
implements the [`GlobalIdentityHandlerFactory`] trait, which extends
88+
[`IdentityHandlerFactory<I>`] for each resource id type `I`. This
89+
trait, in turn, has a `spawn` method that constructs an
90+
`IdentityHandler<I>` for the `Global` to use.
91+
92+
What this means is that the types of resource creation functions'
93+
`id_in` arguments depend on the `Global`'s `G` type parameter. A
94+
`Global<G>`'s `IdentityHandler<I>` implementation is:
95+
96+
```ignore
97+
<G as IdentityHandlerFactory<I>>::Filter
98+
```
99+
100+
where `Filter` is an associated type of the `IdentityHandlerFactory` trait.
101+
Thus, its `id_in` type is:
102+
103+
```ignore
104+
<<G as IdentityHandlerFactory<I>>::Filter as IdentityHandler<I>>::Input
105+
```
106+
107+
The [`Input<G, I>`] type is an alias for this construction.
108+
109+
## Id allocation and streaming
110+
111+
Perhaps surprisingly, allowing users to assign resource ids themselves
112+
enables major performance improvements in some applications.
113+
114+
The `wgpu_core` API is designed for use by Firefox's [WebGPU]
115+
implementation. For security, web content and GPU use must be kept
116+
segregated in separate processes, with all interaction between them
117+
mediated by an inter-process communication protocol. As web content uses
118+
the WebGPU API, the content process sends messages to the GPU process,
119+
which interacts with the platform's GPU APIs on content's behalf,
120+
occasionally sending results back.
121+
122+
In a classic Rust API, a resource allocation function takes parameters
123+
describing the resource to create, and if creation succeeds, it returns
124+
the resource id in a `Result::Ok` value. However, this design is a poor
125+
fit for the split-process design described above: content must wait for
126+
the reply to its buffer-creation message (say) before it can know which
127+
id it can use in the next message that uses that buffer. On a common
128+
usage pattern, the classic Rust design imposes the latency of a full
129+
cross-process round trip.
130+
131+
We can avoid incurring these round-trip latencies simply by letting the
132+
content process assign resource ids itself. With this approach, content
133+
can choose an id for the new buffer, send a message to create the
134+
buffer, and then immediately send the next message operating on that
135+
buffer, since it already knows its id. Allowing content and GPU process
136+
activity to be pipelined greatly improves throughput.
137+
138+
To help propagate errors correctly in this style of usage, when resource
139+
creation fails, the id supplied for that resource is marked to indicate
140+
as much, allowing subsequent operations using that id to be properly
141+
flagged as errors as well.
142+
143+
[`gfx_select`]: crate::gfx_select
144+
[`Input`]: IdentityHandler::Input
145+
[`process`]: IdentityHandler::process
146+
[`Id<R>`]: crate::id::Id
147+
[wrapped in a mutex]: trait.IdentityHandler.html#impl-IdentityHandler%3CI%3E-for-Mutex%3CIdentityManager%3E
148+
[WebGPU]: https://www.w3.org/TR/webgpu/
149+
150+
*/
151+
1152
use crate::{
2153
binding_model::{BindGroup, BindGroupLayout, PipelineLayout},
3154
command::{CommandBuffer, RenderBundle},
@@ -36,6 +187,9 @@ use std::{fmt::Debug, marker::PhantomData, mem, ops};
36187
/// - `IdentityManager` reuses the index values of freed ids before returning
37188
/// ids with new index values. Freed vector entries get reused.
38189
///
190+
/// See the module-level documentation for an overview of how this
191+
/// fits together.
192+
///
39193
/// [`Id`]: crate::id::Id
40194
/// [`Backend`]: wgt::Backend;
41195
/// [`alloc`]: IdentityManager::alloc
@@ -431,9 +585,24 @@ impl<'a, T> Drop for Token<'a, T> {
431585
}
432586
}
433587

588+
/// A type that can build true ids from proto-ids, and free true ids.
589+
///
590+
/// For some implementations, the true id is based on the proto-id.
591+
/// The caller is responsible for providing well-allocated proto-ids.
592+
///
593+
/// For other implementations, the proto-id carries no information
594+
/// (it's `()`, say), and this `IdentityHandler` type takes care of
595+
/// allocating a fresh true id.
596+
///
597+
/// See the module-level documentation for details.
434598
pub trait IdentityHandler<I>: Debug {
599+
/// The type of proto-id consumed by this filter, to produce a true id.
435600
type Input: Clone + Debug;
601+
602+
/// Given a proto-id value `id`, return a true id for `backend`.
436603
fn process(&self, id: Self::Input, backend: Backend) -> I;
604+
605+
/// Free the true id `id`.
437606
fn free(&self, id: I);
438607
}
439608

@@ -447,11 +616,28 @@ impl<I: id::TypedId + Debug> IdentityHandler<I> for Mutex<IdentityManager> {
447616
}
448617
}
449618

619+
/// A type that can produce [`IdentityHandler`] filters for ids of type `I`.
620+
///
621+
/// See the module-level documentation for details.
450622
pub trait IdentityHandlerFactory<I> {
623+
/// The type of filter this factory constructs.
624+
///
625+
/// "Filter" and "handler" seem to both mean the same thing here:
626+
/// something that can produce true ids from proto-ids.
451627
type Filter: IdentityHandler<I>;
628+
629+
/// Create an [`IdentityHandler<I>`] implementation that can
630+
/// transform proto-ids into ids of type `I`.
631+
///
632+
/// [`IdentityHandler<I>`]: IdentityHandler
452633
fn spawn(&self) -> Self::Filter;
453634
}
454635

636+
/// A global identity handler factory based on [`IdentityManager`].
637+
///
638+
/// Each of this type's `IdentityHandlerFactory<I>::spawn` methods
639+
/// returns a `Mutex<IdentityManager<I>>`, which allocates fresh `I`
640+
/// ids itself, and takes `()` as its proto-id type.
455641
#[derive(Debug)]
456642
pub struct IdentityManagerFactory;
457643

@@ -462,6 +648,8 @@ impl<I: id::TypedId + Debug> IdentityHandlerFactory<I> for IdentityManagerFactor
462648
}
463649
}
464650

651+
/// A factory that can build [`IdentityHandler`]s for all resource
652+
/// types.
465653
pub trait GlobalIdentityHandlerFactory:
466654
IdentityHandlerFactory<id::AdapterId>
467655
+ IdentityHandlerFactory<id::DeviceId>

0 commit comments

Comments
 (0)