Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
bevyengine
GitHub Repository: bevyengine/bevy
Path: blob/main/crates/bevy_render/src/render_resource/atomic_pod.rs
30636 views
1
//! Utilities that allow updating of large POD structures from multiple threads.
2
//!
3
//! In a select few cases, for performance reasons, we want to update "plain
4
//! old data"—data without any pointers—from helper threads, without having to
5
//! send the new data over a channel first. These utilities allow code that
6
//! needs to perform this operation to do so without dropping down to unsafe
7
//! code.
8
//!
9
//! Note that, while this operation is always *memory* safe, it isn't free of
10
//! potential data races. Updating large amounts of POD atomically, word by
11
//! word, amplifies the consequences of data races, because write hazards can
12
//! result in data "slicing". That is, one thread can see the results of a copy
13
//! operation in progress, a situation which ordinary atomics prevent. So you
14
//! should use the functionality in here sparingly and only when measured
15
//! performance concerns justify it.
16
17
use bytemuck::Pod;
18
19
/// Data that can be converted to an array of [`std::sync::atomic::AtomicU32`]
20
/// values.
21
///
22
/// That array is known as the *blob* ([`Self::Blob`]). The trait provides
23
/// methods to copy data into and out of the blob type.
24
///
25
/// Note that, while implementing this trait isn't unsafe, it can be tedious,
26
/// and in any case implementing [`AtomicPodBlob`] *is* unsafe. Therefore, you
27
/// should almost always use the `impl_atomic_pod!` macro to produce
28
/// implementations of this trait.
29
pub trait AtomicPod: Pod + Default + Send + Sync + 'static {
30
/// The *blob* type that allows shared mutation.
31
///
32
/// This type must be an array of [`std::sync::atomic::AtomicU32`]s.
33
/// Because the renderer can't guarantee that, the [`AtomicPodBlob`] trait
34
/// is unsafe. However, the [`crate::impl_atomic_pod`] macro can
35
/// automatically generate safe implementations of [`AtomicPodBlob`] for
36
/// you.
37
type Blob: AtomicPodBlob;
38
39
/// Produces a value of this type from the blob, typically by reading its
40
/// fields one after another atomically.
41
fn read_from_blob(blob: &Self::Blob) -> Self;
42
43
/// Copies the `self` value to the blob, typically by writing its fields one
44
/// after another atomically.
45
///
46
/// Note that, because we're using atomics, the `blob` parameter doesn't
47
/// need a mutable reference.
48
fn write_to_blob(&self, blob: &Self::Blob);
49
}
50
51
/// Describes a type that has the same bit pattern as another type, but is made
52
/// up entirely of an array of [`std::sync::atomic::AtomicU32`] values.
53
///
54
/// This trait enables values of whatever type this mirrors to be written from
55
/// multiple threads. It's memory-safe because the type must be POD. However,
56
/// this doesn't protect against data races; it's possible for safe code to see
57
/// partially-updated values, which might be incorrect. Therefore, use this type
58
/// with caution.
59
///
60
/// The [`crate::impl_atomic_pod`] macro that generates an implementation of
61
/// [`AtomicPod`] automatically generates a blob type that implements
62
/// [`AtomicPodBlob`]. This is the preferred way to implement this trait and
63
/// doesn't require any unsafe code.
64
///
65
/// # Safety
66
///
67
/// This trait must only be implemented by types that are `#[repr(transparent)]`
68
/// wrappers around `[AtomicU32; N]` for some N (where N may legally be 0).
69
/// That's because values implementing this trait are read as a `&[u8]` when
70
/// uploading to the GPU.
71
pub unsafe trait AtomicPodBlob: Default + Send + Sync + 'static {}
72
73
/// A macro that generates a *blob* type that allows a POD type to be updated in
74
/// shared memory.
75
///
76
/// An example of use of this macro:
77
///
78
/// ```
79
/// # use bevy_render::impl_atomic_pod;
80
/// # use bevy_render::render_resource::AtomicPod;
81
/// # use bytemuck::{Pod, Zeroable};
82
/// # use std::mem::offset_of;
83
/// #[derive(Clone, Copy, Default, Pod, Zeroable)]
84
/// #[repr(C)]
85
/// struct Foo {
86
/// a: u32,
87
/// b: u32,
88
/// }
89
/// impl_atomic_pod!(
90
/// Foo,
91
/// FooBlob,
92
/// field(a: u32, a, set_a),
93
/// field(b: u32, b, set_b),
94
/// );
95
/// ```
96
///
97
/// The first argument to this macro is the name of the type that you wish to be
98
/// updatable in shared memory. The second argument is the name of a "blob"
99
/// type: conventionally, it matches the name of the type with `Blob` appended.
100
///
101
/// Afterward follow optional *field getter and setter* declarations. These
102
/// declarations direct the [`crate::impl_atomic_pod`] macro to create
103
/// convenience accessor and mutation methods that allow fields of the blob
104
/// value to be accessed and mutated. The first argument of `field` is the name
105
/// of the field, a `:`, and the type of the field. The second argument is the
106
/// name that this macro should assign the accessor method, and the third,
107
/// optional, argument is the name that this macro should give the mutator
108
/// method.
109
///
110
/// This macro generates (1) the `struct` corresponding to the blob type; (2)
111
/// the implementation of `AtomicPod` for the POD type; (3) the unsafe
112
/// implementation of `AtomicPodBlob`; (4) an inherent implementation of
113
/// `AtomicPodBlob` that contains accessor and mutator methods as directed.
114
///
115
/// The POD type must have a size that's a multiple of 4 bytes, as must the
116
/// types of any fields that are named in `field` declarations.
117
#[macro_export]
118
macro_rules! impl_atomic_pod {
119
(
120
$pod_ty: ty,
121
$blob_ty: ident
122
$(, field($field_name: ident : $field_ty: ty, $getter: ident $(, $($setter: ident)?)?))*
123
$(,)?
124
) => {
125
#[derive(::core::default::Default, ::bevy_derive::Deref, ::bevy_derive::DerefMut)]
126
#[repr(transparent)]
127
pub struct $blob_ty(
128
pub [::core::sync::atomic::AtomicU32; ::core::mem::size_of::<$pod_ty>() / 4],
129
);
130
131
impl $crate::render_resource::AtomicPod for $pod_ty {
132
type Blob = $blob_ty;
133
134
fn read_from_blob(blob: &Self::Blob) -> Self {
135
const _ASSERT_POD_TYPE_SIZE: () = ::core::assert!(
136
::core::mem::size_of::<$pod_ty>() % 4 == 0
137
);
138
139
// Read the value word by word.
140
// Note that relaxed atomics, at the hardware level, are as
141
// cheap as regular loads on x86-64 and AArch64.
142
let nonatomic_data: [u32; ::core::mem::size_of::<$pod_ty>() / 4] =
143
::core::array::from_fn(|i| {
144
blob.0[i].load(::bevy_platform::sync::atomic::Ordering::Relaxed)
145
});
146
::bytemuck::must_cast(nonatomic_data)
147
}
148
149
fn write_to_blob(&self, blob: &Self::Blob) {
150
// Store the value word by word.
151
// Note that relaxed atomics, at the hardware level, are as
152
// cheap as regular loads on x86-64 and AArch64.
153
let src: [u32; ::core::mem::size_of::<$pod_ty>() / 4] =
154
::bytemuck::must_cast(*self);
155
for (dest, src) in blob.0.iter().zip(src.iter()) {
156
dest.store(*src, ::bevy_platform::sync::atomic::Ordering::Relaxed);
157
}
158
}
159
}
160
161
// SAFETY: Atomic POD blobs must be bit-castable to a flat list of
162
// `AtomicU32`s, which we ensured above.
163
unsafe impl $crate::render_resource::AtomicPodBlob for $blob_ty {}
164
165
impl<'a> ::core::convert::From<&'a $pod_ty> for $blob_ty {
166
fn from(pod: &'a $pod_ty) -> Self {
167
let blob = Self::default();
168
pod.write_to_blob(&blob);
169
blob
170
}
171
}
172
173
impl $blob_ty {
174
$(
175
$(
176
pub fn $getter(&self) -> $field_ty {
177
const _ASSERT_FIELD_SIZE: () = ::core::assert!(
178
::core::mem::size_of::<$field_ty>() % 4 == 0
179
);
180
181
// Extract the field we're looking for.
182
// Note that the field must have a size that is a
183
// multiple of 4.
184
let words: [u32; ::core::mem::size_of::<$field_ty>() / 4] =
185
::core::array::from_fn(|i| {
186
self.0[::core::mem::offset_of!($pod_ty, $field_name) / 4 + i]
187
.load(::bevy_platform::sync::atomic::Ordering::Relaxed)
188
});
189
*::bytemuck::must_cast_ref(&words)
190
}
191
192
$(
193
pub fn $setter(&self, value: $field_ty) {
194
// Insert the appropriate field.
195
// Note that the field must have a size that is a
196
// multiple of 4.
197
let words: [u32; ::core::mem::size_of::<$field_ty>() / 4] =
198
::bytemuck::must_cast(value);
199
for i in 0..(::core::mem::size_of::<$field_ty>() / 4) {
200
self.0[::core::mem::offset_of!($pod_ty, $field_name) / 4 + i]
201
.store(words[i], ::bevy_platform::sync::atomic::Ordering::Relaxed);
202
}
203
}
204
)?
205
)*
206
)?
207
}
208
};
209
}
210
211
impl_atomic_pod!((), AtomicPodUnitBlob);
212
213