Skip to content

Commit 51ee00f

Browse files
committed
doc fixes
1 parent 3917e58 commit 51ee00f

1 file changed

Lines changed: 76 additions & 67 deletions

File tree

  • wgpu-remote/src/transport

wgpu-remote/src/transport/mod.rs

Lines changed: 76 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -1,57 +1,42 @@
11
/*! The [`Sender`] and [`Receiver`] traits, for wgpu transports.
22
33
This module defines the [`Sender`] and [`Receiver`] traits, representing the
4-
endpoints of a one-way communications channel between a wgpu client and server.
5-
A single `Sender` sends messages to a single `Receiver`. A `Sender` can also
6-
create memory segments shared with the `Receiver` to hold message content and
7-
other common data.
8-
9-
Remote wgpu access requires two `Sender`/`Receiver` pairs, providing
10-
communication in each direction between client and server. This documentation
11-
refers to an interacting client and server as "counterparts".
12-
13-
`Sender` and `Receiver` are low-level traits. Using them entails working with
14-
raw pointers to shared memory segments, so users will generally need to build
15-
higher-level abstractions around them them to provide well-typed interfaces,
16-
prevent data races, batch messages to reduce IPC overhead, and so on.
17-
18-
For example, to build a communications channel carrying typed messages that was
19-
generic over `Sender` implementations, you could use `Sender` to create and map
20-
a shared memory segment, and then serialize the messages into the shared memory.
21-
When it became full, or a response was needed, you would call
22-
[`Sender::send_message`] to alert your counterpart to the whole batch of
23-
messages. Your [`Receiver::receive_message`] implementation in the counterpart
24-
would then map the shared memory segment, deserialize the messages from the
25-
given range, and process them.
26-
27-
## Senders
28-
29-
A [`Sender`] implementation is responsible for interacting with the operating
30-
system to create memory segments that are shared with its `Receiver`, and
31-
sending unbuffered messages to the `Receiver`. A `Sender` should be agnostic to
32-
the actual content of those memory segments and messages; only the `Sender`'s
33-
user knows their interpretation.
4+
endpoints of a one-way communications channel between a wgpu client and server,
5+
based on shared memory. A single `Sender` sends messages to a single `Receiver`.
6+
For two-way communication, each side needs its own `Sender` and `Receiver`. This
7+
documentation refers to the two sides of a connection as "counterparts".
8+
9+
The [`Sender`] trait abstracts over platform APIs to define a portable but
10+
low-level interface for communication based on shared memory. To keep [`Sender`]
11+
easy to implement, it is a low-level interface: byte-oriented, unbuffered, and
12+
with no enforcement of synchronized access to shared memory. However, it should
13+
be sufficient for applications to build well-typed, thread-safe abstractions
14+
that are generic over any `Sender` implementation.
15+
16+
As messages arrive from the counterpart, a `Sender` implementation passes them
17+
to a [`Receiver`] implementation provided by the user, which serves the role of
18+
"callback" or "event handler". Like [`Sender`], [`Receiver`] is a low-level,
19+
byte-oriented interface; interpretation of the contents as data meaningful to
20+
the application is left to the [`Receiver`] implementation.
3421
3522
The `Sender` trait is meant to be easy to implement in terms of a wide range of
3623
operating system mechanisms:
3724
38-
- A Unix implementation might use [`mmap`] to create memory segments, and then
39-
send `SCM_RIGHTS` messages over [`AF_UNIX`] sockets to share them with their
40-
counterpart.
25+
- A Unix implementation might use [`AF_UNIX`] address family sockets
26+
(also known as "Unix domain sockets") to exchange messages. Shared
27+
memory segments would be created with [`memfd_create`], conveyed to
28+
the counterpart over the socket using `SCM_RIGHTS` ancillary
29+
messages, and mapped into each side's address space with [`mmap`].
4130
42-
- A Windows implementation might use [`CreateFileMappingW`] to create memory
43-
segments, and then use [`DuplicateHandle`] to share them.
31+
- A Windows implementation might use ordinary sockets for
32+
communication, [`CreateFileMappingW`] to create memory segments, and
33+
then use [`DuplicateHandle`] to share them.
4434
4535
- For testing, the [`transport::local`] module provides a [`Sender`]
4636
implementation in which both sides of the connection live in the
4737
same process. "Shared" memory is simply an ordinary block of memory.
4838
49-
## Receivers
50-
51-
An implementation of the [`Receiver`] trait serves as the callback invoked when
52-
messages are received. It is meant to be implemented by users of the transport.
53-
54-
Exactly how `Receiver`s get called when messages arrive is specific to the
39+
Exactly how [`Receiver`]s get called when messages arrive is specific to the
5540
`Sender` implementation:
5641
5742
- A `Sender` implementation might spawn a thread to read messages from a socket
@@ -61,13 +46,10 @@ Exactly how `Receiver`s get called when messages arrive is specific to the
6146
some sort of platform event loop, and have that listener call the
6247
`Receiver` when appropriate.
6348
64-
Whatever the case, the `Sender` implementation should document this behavior.
65-
For example, users may need to know which thread the `Receiver` is invoked on to
66-
avoid deadlocks.
67-
68-
The [`Sender`] and [`Receiver`] traits are meant to integrate smoothly with
69-
existing interprocess communication mechanisms and event loops, like Firefox's
70-
[`IPDL`] and [`nsISerialEventTarget`].
49+
While the [`Sender`] and [`Receiver`] traits can be implemented directly in
50+
terms of operating system facilities, they are also meant to integrate smoothly
51+
with existing interprocess communication mechanisms and event loops, like
52+
Firefox's [`IPDL`] and [`nsISerialEventTarget`].
7153
7254
Although these traits are designed for use with `wgpu`, this module attempts to
7355
fully specify the contract between a transport and its user, independently of
@@ -77,43 +59,70 @@ on.
7759
7860
## Shared memory
7961
80-
The [`Sender`] and [`Receiver`] traits are meant for use in situations where the
81-
endpoints can share memory with each other, such that writes to a shared memory
82-
segment on one side are immediately visible on the other.
62+
The [`Sender`] and [`Receiver`] traits are intended for use in situations where
63+
the endpoints can share memory with each other, such that writes to a shared
64+
memory segment on one side are immediately visible on the other.
8365
8466
- [`Sender::allocate_shared_memory`] creates a shared memory segment, and
8567
returns an id by which both counterparts can refer to it.
8668
8769
- [`Sender::send_message`] sends the counterpart a message whose content resides
88-
in a shared memory segment.
70+
in a given shared memory segment.
8971
9072
- [`Sender::map_shared_memory`] takes a given shared memory segment and makes
9173
it visible in the caller's address space.
9274
9375
- [`Sender::close_shared_memory`] frees a shared memory segment.
9476
95-
However, it is possible to implement `Sender` without using shared memory (for
96-
example, over a network connection) if the transport's user is willing to make
97-
calls to [`Sender::flush_shared_memory_range`] to explicitly indicate which
98-
regions of its shared memory segments have new content that must be copied to
99-
the counterpart. If used correctly, this interface allows the transport to
100-
behave as expected whether or not shared memory is used; the shared memory
101-
becomes merely a transparent optimization, not an architectural feature.
77+
The application can create as many shared memory segments as it needs. Shared
78+
memory handles are transparent newtypes around integers, so they are easy to
79+
refer to in messages or data structures held in other shared memory segments.
80+
81+
For example, a WebGPU implementation might create a shared memory segment to
82+
hold a queue of API calls made by web content that are waiting to be conveyed to
83+
a GPU sandbox process for execution; and it might create additional shared
84+
memory segments representing mappable buffer contents.
85+
86+
## Using `Sender` without shared memory
87+
88+
It is possible to implement `Sender` without using shared memory (for example,
89+
over a network connection), if the transport's user is willing to make calls to
90+
[`Sender::flush_shared_memory_range`] to explicitly indicate which regions of
91+
its shared memory segments have new content that must be copied to the
92+
counterpart. Although this interface is trickier to use, it allows the transport
93+
to behave as expected whether or not it can actually create memory segments
94+
shared with the counterpart, which in turn allows the application to work over a
95+
broader range of transports. Shared memory becomes merely a transparent
96+
optimization, not an architectural feature.
10297
10398
The requirement to flush modified regions is not as onerous as one might expect.
104-
In practice, the steps needed to ensure that a shared-memory interaction is free
105-
of data races often also make it apparent where flushes are necessary. For
106-
example, to ensure consistent behavior between browsers, WebGPU's buffer API
107-
fully separates CPU access from GPU access; WebGPU's mapping and unmapping steps
108-
indicate where flushes on some underlying transport would need to occur.
99+
In practice, it is often the case that, by the time one has ensured that the
100+
application's interactions with shared memory segments are free of data races,
101+
it is also apparent where flushes would be necessary.
102+
103+
For example, although WebGPU's buffer mapping behavior is intended to be
104+
implemented using memory regions that are shared between the web content process
105+
and a sandboxed process that interacts directly with the GPU, it is also
106+
possible to implement WebGPU without shared memory. To ensure consistent
107+
behavior across browsers and GPUs, WebGPU's buffer API segregates web content
108+
access from GPU access: web content can access a buffer only after mapping it,
109+
and the GPU can access a buffer only when it is unmapped. These ownership
110+
transitioning operations are where flushes would need to occur, in the case that
111+
the web content and GPU process do not actually share memory:
112+
113+
- Before a buffer is mapped by web content, the GPU process must flush any
114+
regions of the buffer it may have written to.
115+
116+
- When web content unmaps a buffer, the content process must flush any regions
117+
of the buffer web content modified (conservatively, the entire buffer).
109118
110119
An implementation of [`Sender`] may guarantee that it uses shared memory. Users
111-
of such an implementation need not call [`flush_shared_memory_range`].
112-
Naturally, taking advantage of this looser contract limits which transport
120+
of such an implementation need not call [`flush_shared_memory_range`], but
121+
naturally, taking advantage of this looser contract limits which transport
113122
implementations they can use.
114123
115124
[`Sender`] implementations may even decide whether or not to use shared memory
116-
dynamically. Users of such implementations should assume the worst, and call
125+
dynamically. Users of such implementations mustx assume the worst, and call
117126
[`flush_shared_memory_range`] as described in its documentation.
118127
119128
In Rust, data races are undefined behavior. Users of these traits are

0 commit comments

Comments
 (0)