Path: blob/main/crates/bevy_render/src/render_resource/atomic_pod.rs
30636 views
//! Utilities that allow updating of large POD structures from multiple threads.1//!2//! In a select few cases, for performance reasons, we want to update "plain3//! old data"—data without any pointers—from helper threads, without having to4//! send the new data over a channel first. These utilities allow code that5//! needs to perform this operation to do so without dropping down to unsafe6//! code.7//!8//! Note that, while this operation is always *memory* safe, it isn't free of9//! potential data races. Updating large amounts of POD atomically, word by10//! word, amplifies the consequences of data races, because write hazards can11//! result in data "slicing". That is, one thread can see the results of a copy12//! operation in progress, a situation which ordinary atomics prevent. So you13//! should use the functionality in here sparingly and only when measured14//! performance concerns justify it.1516use bytemuck::Pod;1718/// Data that can be converted to an array of [`std::sync::atomic::AtomicU32`]19/// values.20///21/// That array is known as the *blob* ([`Self::Blob`]). The trait provides22/// methods to copy data into and out of the blob type.23///24/// Note that, while implementing this trait isn't unsafe, it can be tedious,25/// and in any case implementing [`AtomicPodBlob`] *is* unsafe. Therefore, you26/// should almost always use the `impl_atomic_pod!` macro to produce27/// implementations of this trait.28pub trait AtomicPod: Pod + Default + Send + Sync + 'static {29/// The *blob* type that allows shared mutation.30///31/// This type must be an array of [`std::sync::atomic::AtomicU32`]s.32/// Because the renderer can't guarantee that, the [`AtomicPodBlob`] trait33/// is unsafe. However, the [`crate::impl_atomic_pod`] macro can34/// automatically generate safe implementations of [`AtomicPodBlob`] for35/// you.36type Blob: AtomicPodBlob;3738/// Produces a value of this type from the blob, typically by reading its39/// fields one after another atomically.40fn read_from_blob(blob: &Self::Blob) -> Self;4142/// Copies the `self` value to the blob, typically by writing its fields one43/// after another atomically.44///45/// Note that, because we're using atomics, the `blob` parameter doesn't46/// need a mutable reference.47fn write_to_blob(&self, blob: &Self::Blob);48}4950/// Describes a type that has the same bit pattern as another type, but is made51/// up entirely of an array of [`std::sync::atomic::AtomicU32`] values.52///53/// This trait enables values of whatever type this mirrors to be written from54/// multiple threads. It's memory-safe because the type must be POD. However,55/// this doesn't protect against data races; it's possible for safe code to see56/// partially-updated values, which might be incorrect. Therefore, use this type57/// with caution.58///59/// The [`crate::impl_atomic_pod`] macro that generates an implementation of60/// [`AtomicPod`] automatically generates a blob type that implements61/// [`AtomicPodBlob`]. This is the preferred way to implement this trait and62/// doesn't require any unsafe code.63///64/// # Safety65///66/// This trait must only be implemented by types that are `#[repr(transparent)]`67/// wrappers around `[AtomicU32; N]` for some N (where N may legally be 0).68/// That's because values implementing this trait are read as a `&[u8]` when69/// uploading to the GPU.70pub unsafe trait AtomicPodBlob: Default + Send + Sync + 'static {}7172/// A macro that generates a *blob* type that allows a POD type to be updated in73/// shared memory.74///75/// An example of use of this macro:76///77/// ```78/// # use bevy_render::impl_atomic_pod;79/// # use bevy_render::render_resource::AtomicPod;80/// # use bytemuck::{Pod, Zeroable};81/// # use std::mem::offset_of;82/// #[derive(Clone, Copy, Default, Pod, Zeroable)]83/// #[repr(C)]84/// struct Foo {85/// a: u32,86/// b: u32,87/// }88/// impl_atomic_pod!(89/// Foo,90/// FooBlob,91/// field(a: u32, a, set_a),92/// field(b: u32, b, set_b),93/// );94/// ```95///96/// The first argument to this macro is the name of the type that you wish to be97/// updatable in shared memory. The second argument is the name of a "blob"98/// type: conventionally, it matches the name of the type with `Blob` appended.99///100/// Afterward follow optional *field getter and setter* declarations. These101/// declarations direct the [`crate::impl_atomic_pod`] macro to create102/// convenience accessor and mutation methods that allow fields of the blob103/// value to be accessed and mutated. The first argument of `field` is the name104/// of the field, a `:`, and the type of the field. The second argument is the105/// name that this macro should assign the accessor method, and the third,106/// optional, argument is the name that this macro should give the mutator107/// method.108///109/// This macro generates (1) the `struct` corresponding to the blob type; (2)110/// the implementation of `AtomicPod` for the POD type; (3) the unsafe111/// implementation of `AtomicPodBlob`; (4) an inherent implementation of112/// `AtomicPodBlob` that contains accessor and mutator methods as directed.113///114/// The POD type must have a size that's a multiple of 4 bytes, as must the115/// types of any fields that are named in `field` declarations.116#[macro_export]117macro_rules! impl_atomic_pod {118(119$pod_ty: ty,120$blob_ty: ident121$(, field($field_name: ident : $field_ty: ty, $getter: ident $(, $($setter: ident)?)?))*122$(,)?123) => {124#[derive(::core::default::Default, ::bevy_derive::Deref, ::bevy_derive::DerefMut)]125#[repr(transparent)]126pub struct $blob_ty(127pub [::core::sync::atomic::AtomicU32; ::core::mem::size_of::<$pod_ty>() / 4],128);129130impl $crate::render_resource::AtomicPod for $pod_ty {131type Blob = $blob_ty;132133fn read_from_blob(blob: &Self::Blob) -> Self {134const _ASSERT_POD_TYPE_SIZE: () = ::core::assert!(135::core::mem::size_of::<$pod_ty>() % 4 == 0136);137138// Read the value word by word.139// Note that relaxed atomics, at the hardware level, are as140// cheap as regular loads on x86-64 and AArch64.141let nonatomic_data: [u32; ::core::mem::size_of::<$pod_ty>() / 4] =142::core::array::from_fn(|i| {143blob.0[i].load(::bevy_platform::sync::atomic::Ordering::Relaxed)144});145::bytemuck::must_cast(nonatomic_data)146}147148fn write_to_blob(&self, blob: &Self::Blob) {149// Store the value word by word.150// Note that relaxed atomics, at the hardware level, are as151// cheap as regular loads on x86-64 and AArch64.152let src: [u32; ::core::mem::size_of::<$pod_ty>() / 4] =153::bytemuck::must_cast(*self);154for (dest, src) in blob.0.iter().zip(src.iter()) {155dest.store(*src, ::bevy_platform::sync::atomic::Ordering::Relaxed);156}157}158}159160// SAFETY: Atomic POD blobs must be bit-castable to a flat list of161// `AtomicU32`s, which we ensured above.162unsafe impl $crate::render_resource::AtomicPodBlob for $blob_ty {}163164impl<'a> ::core::convert::From<&'a $pod_ty> for $blob_ty {165fn from(pod: &'a $pod_ty) -> Self {166let blob = Self::default();167pod.write_to_blob(&blob);168blob169}170}171172impl $blob_ty {173$(174$(175pub fn $getter(&self) -> $field_ty {176const _ASSERT_FIELD_SIZE: () = ::core::assert!(177::core::mem::size_of::<$field_ty>() % 4 == 0178);179180// Extract the field we're looking for.181// Note that the field must have a size that is a182// multiple of 4.183let words: [u32; ::core::mem::size_of::<$field_ty>() / 4] =184::core::array::from_fn(|i| {185self.0[::core::mem::offset_of!($pod_ty, $field_name) / 4 + i]186.load(::bevy_platform::sync::atomic::Ordering::Relaxed)187});188*::bytemuck::must_cast_ref(&words)189}190191$(192pub fn $setter(&self, value: $field_ty) {193// Insert the appropriate field.194// Note that the field must have a size that is a195// multiple of 4.196let words: [u32; ::core::mem::size_of::<$field_ty>() / 4] =197::bytemuck::must_cast(value);198for i in 0..(::core::mem::size_of::<$field_ty>() / 4) {199self.0[::core::mem::offset_of!($pod_ty, $field_name) / 4 + i]200.store(words[i], ::bevy_platform::sync::atomic::Ordering::Relaxed);201}202}203)?204)*205)?206}207};208}209210impl_atomic_pod!((), AtomicPodUnitBlob);211212213