Skip to content

Commit 189df30

Browse files
committed
use bytemuck crate instead of Byteable trait (#2183)
This gets rid of multiple unsafe blocks that we had to maintain ourselves, and instead depends on library that's commonly used and supported by the ecosystem. We also get support for glam types for free. There is still some things to clear up with the `Bytes` trait, but that is a bit more substantial change and can be done separately. Also there are already separate efforts to use `crevice` crate, so I've just added that as a TODO.
1 parent 0c096d3 commit 189df30

File tree

14 files changed

+98
-228
lines changed

14 files changed

+98
-228
lines changed

crates/bevy_core/Cargo.toml

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,14 @@ keywords = ["bevy"]
1414

1515

1616
[dependencies]
17+
# bevy
1718
bevy_app = { path = "../bevy_app", version = "0.5.0" }
1819
bevy_derive = { path = "../bevy_derive", version = "0.5.0" }
1920
bevy_ecs = { path = "../bevy_ecs", version = "0.5.0" }
2021
bevy_math = { path = "../bevy_math", version = "0.5.0" }
2122
bevy_reflect = { path = "../bevy_reflect", version = "0.5.0", features = ["bevy"] }
2223
bevy_tasks = { path = "../bevy_tasks", version = "0.5.0" }
23-
bevy_utils = { path = "../bevy_utils", version = "0.5.0" }
24+
bevy_utils = { path = "../bevy_utils", version = "0.5.0" }
25+
26+
# other
27+
bytemuck = "1.5"

crates/bevy_core/src/bytes.rs

Lines changed: 13 additions & 149 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,14 @@
1-
use bevy_math::{Mat4, Vec2, Vec3, Vec4};
2-
31
pub use bevy_derive::Bytes;
42

3+
// NOTE: we can reexport common traits and methods from bytemuck to avoid requiring dependency most of
4+
// the time, but unfortunately we can't use derive macros that way due to hardcoded path in generated code.
5+
pub use bytemuck::{bytes_of, cast_slice, Pod, Zeroable};
6+
7+
// FIXME: `Bytes` trait doesn't specify the expected encoding format,
8+
// which means types that implement it have to know what format is expected
9+
// and can only implement one encoding at a time.
10+
// TODO: Remove `Bytes` and `FromBytes` in favour of `crevice` crate.
11+
512
/// Converts the implementing type to bytes by writing them to a given buffer
613
pub trait Bytes {
714
/// Converts the implementing type to bytes by writing them to a given buffer
@@ -11,29 +18,19 @@ pub trait Bytes {
1118
fn byte_len(&self) -> usize;
1219
}
1320

14-
/// A trait that indicates that it is safe to cast the type to a byte array reference.
15-
pub unsafe trait Byteable: Copy + Sized {}
16-
1721
impl<T> Bytes for T
1822
where
19-
T: Byteable,
23+
T: Pod,
2024
{
2125
fn write_bytes(&self, buffer: &mut [u8]) {
22-
let bytes = self.as_bytes();
23-
buffer[0..self.byte_len()].copy_from_slice(bytes)
26+
buffer[0..self.byte_len()].copy_from_slice(bytes_of(self))
2427
}
2528

2629
fn byte_len(&self) -> usize {
2730
std::mem::size_of::<Self>()
2831
}
2932
}
3033

31-
/// Reads the implementing type as a byte array reference
32-
pub trait AsBytes {
33-
/// Reads the implementing type as a byte array reference
34-
fn as_bytes(&self) -> &[u8];
35-
}
36-
3734
/// Converts a byte array to `Self`
3835
pub trait FromBytes {
3936
/// Converts a byte array to `Self`
@@ -42,7 +39,7 @@ pub trait FromBytes {
4239

4340
impl<T> FromBytes for T
4441
where
45-
T: Byteable,
42+
T: Pod,
4643
{
4744
fn from_bytes(bytes: &[u8]) -> Self {
4845
assert_eq!(
@@ -55,128 +52,6 @@ where
5552
}
5653
}
5754

58-
impl<T> AsBytes for T
59-
where
60-
T: Byteable,
61-
{
62-
fn as_bytes(&self) -> &[u8] {
63-
let len = std::mem::size_of::<T>();
64-
unsafe { core::slice::from_raw_parts(self as *const Self as *const u8, len) }
65-
}
66-
}
67-
68-
impl<'a, T> AsBytes for [T]
69-
where
70-
T: Byteable,
71-
{
72-
fn as_bytes(&self) -> &[u8] {
73-
let len = std::mem::size_of_val(self);
74-
unsafe { core::slice::from_raw_parts(self as *const Self as *const u8, len) }
75-
}
76-
}
77-
78-
unsafe impl<T, const N: usize> Byteable for [T; N] where T: Byteable {}
79-
80-
unsafe impl Byteable for u8 {}
81-
unsafe impl Byteable for u16 {}
82-
unsafe impl Byteable for u32 {}
83-
unsafe impl Byteable for u64 {}
84-
unsafe impl Byteable for usize {}
85-
unsafe impl Byteable for i8 {}
86-
unsafe impl Byteable for i16 {}
87-
unsafe impl Byteable for i32 {}
88-
unsafe impl Byteable for i64 {}
89-
unsafe impl Byteable for isize {}
90-
unsafe impl Byteable for f32 {}
91-
unsafe impl Byteable for f64 {}
92-
unsafe impl Byteable for Vec2 {}
93-
// NOTE: Vec3 actually takes up the size of 4 floats / 16 bytes due to SIMD. This is actually
94-
// convenient because GLSL uniform buffer objects pad Vec3s to be 16 bytes.
95-
unsafe impl Byteable for Vec3 {}
96-
unsafe impl Byteable for Vec4 {}
97-
98-
impl Bytes for Mat4 {
99-
fn write_bytes(&self, buffer: &mut [u8]) {
100-
let array = self.to_cols_array();
101-
array.write_bytes(buffer);
102-
}
103-
104-
fn byte_len(&self) -> usize {
105-
std::mem::size_of::<Self>()
106-
}
107-
}
108-
109-
impl FromBytes for Mat4 {
110-
fn from_bytes(bytes: &[u8]) -> Self {
111-
let array = <[f32; 16]>::from_bytes(bytes);
112-
Mat4::from_cols_array(&array)
113-
}
114-
}
115-
116-
impl<T> Bytes for Option<T>
117-
where
118-
T: Bytes,
119-
{
120-
fn write_bytes(&self, buffer: &mut [u8]) {
121-
if let Some(val) = self {
122-
val.write_bytes(buffer)
123-
}
124-
}
125-
126-
fn byte_len(&self) -> usize {
127-
self.as_ref().map_or(0, |val| val.byte_len())
128-
}
129-
}
130-
131-
impl<T> FromBytes for Option<T>
132-
where
133-
T: FromBytes,
134-
{
135-
fn from_bytes(bytes: &[u8]) -> Self {
136-
if bytes.is_empty() {
137-
None
138-
} else {
139-
Some(T::from_bytes(bytes))
140-
}
141-
}
142-
}
143-
144-
impl<T> Bytes for Vec<T>
145-
where
146-
T: Byteable,
147-
{
148-
fn write_bytes(&self, buffer: &mut [u8]) {
149-
let bytes = self.as_slice().as_bytes();
150-
buffer[0..self.byte_len()].copy_from_slice(bytes)
151-
}
152-
153-
fn byte_len(&self) -> usize {
154-
self.as_slice().as_bytes().len()
155-
}
156-
}
157-
158-
impl<T> FromBytes for Vec<T>
159-
where
160-
T: Byteable,
161-
{
162-
fn from_bytes(bytes: &[u8]) -> Self {
163-
assert_eq!(
164-
bytes.len() % std::mem::size_of::<T>(),
165-
0,
166-
"Cannot convert byte slice `&[u8]` to type `Vec<{0}>`. Slice length is not a multiple of std::mem::size_of::<{0}>.",
167-
std::any::type_name::<T>(),
168-
);
169-
170-
let len = bytes.len() / std::mem::size_of::<T>();
171-
let mut vec = Vec::<T>::with_capacity(len);
172-
unsafe {
173-
std::ptr::copy_nonoverlapping(bytes.as_ptr(), vec.as_mut_ptr() as *mut u8, bytes.len());
174-
vec.set_len(len);
175-
}
176-
vec
177-
}
178-
}
179-
18055
#[cfg(test)]
18156
mod tests {
18257

@@ -200,17 +75,6 @@ mod tests {
20075
test_round_trip(123f64);
20176
}
20277

203-
#[test]
204-
fn test_vec_bytes_round_trip() {
205-
test_round_trip(vec![1u32, 2u32, 3u32]);
206-
}
207-
208-
#[test]
209-
fn test_option_bytes_round_trip() {
210-
test_round_trip(Some(123u32));
211-
test_round_trip(Option::<u32>::None);
212-
}
213-
21478
#[test]
21579
fn test_vec2_round_trip() {
21680
test_round_trip(Vec2::new(1.0, 2.0));
@@ -233,7 +97,7 @@ mod tests {
23397

23498
#[test]
23599
fn test_array_round_trip() {
236-
test_round_trip([-10i32; 200]);
100+
test_round_trip([-10i32; 1024]);
237101
test_round_trip([Vec2::ZERO, Vec2::ONE, Vec2::Y, Vec2::X]);
238102
}
239103
}

crates/bevy_core/src/float_ord.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use crate::bytes::AsBytes;
1+
use crate::bytes_of;
22
use std::{
33
cmp::Ordering,
44
hash::{Hash, Hasher},
@@ -42,12 +42,12 @@ impl Hash for FloatOrd {
4242
fn hash<H: Hasher>(&self, state: &mut H) {
4343
if self.0.is_nan() {
4444
// Ensure all NaN representations hash to the same value
45-
state.write(f32::NAN.as_bytes())
45+
state.write(bytes_of(&f32::NAN))
4646
} else if self.0 == 0.0 {
4747
// Ensure both zeroes hash to the same value
48-
state.write(0.0f32.as_bytes())
48+
state.write(bytes_of(&0.0f32))
4949
} else {
50-
state.write(self.0.as_bytes());
50+
state.write(bytes_of(&self.0));
5151
}
5252
}
5353
}

crates/bevy_math/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,5 +13,5 @@ license = "MIT"
1313
keywords = ["bevy"]
1414

1515
[dependencies]
16-
glam = { version = "0.14.0", features = ["serde"] }
16+
glam = { version = "0.14.0", features = ["serde", "bytemuck"] }
1717
bevy_reflect = { path = "../bevy_reflect", version = "0.5.0", features = ["bevy"] }

crates/bevy_pbr/Cargo.toml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ license = "MIT"
1313
keywords = ["bevy"]
1414

1515
[dependencies]
16+
# bevy
1617
bevy_app = { path = "../bevy_app", version = "0.5.0" }
1718
bevy_asset = { path = "../bevy_asset", version = "0.5.0" }
1819
bevy_core = { path = "../bevy_core", version = "0.5.0" }
@@ -23,3 +24,7 @@ bevy_reflect = { path = "../bevy_reflect", version = "0.5.0", features = ["bevy"
2324
bevy_render = { path = "../bevy_render", version = "0.5.0" }
2425
bevy_transform = { path = "../bevy_transform", version = "0.5.0" }
2526
bevy_window = { path = "../bevy_window", version = "0.5.0" }
27+
28+
# other
29+
# direct dependency required for derive macro
30+
bytemuck = { version = "1", features = ["derive"] }

crates/bevy_pbr/src/light.rs

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use bevy_core::Byteable;
1+
use bevy_core::{Pod, Zeroable};
22
use bevy_ecs::reflect::ReflectComponent;
33
use bevy_math::Vec3;
44
use bevy_reflect::Reflect;
@@ -27,16 +27,14 @@ impl Default for PointLight {
2727
}
2828

2929
#[repr(C)]
30-
#[derive(Debug, Clone, Copy)]
30+
#[derive(Debug, Clone, Copy, Pod, Zeroable)]
3131
pub(crate) struct PointLightUniform {
3232
pub pos: [f32; 4],
3333
pub color: [f32; 4],
3434
// storing as a `[f32; 4]` for memory alignement
3535
pub light_params: [f32; 4],
3636
}
3737

38-
unsafe impl Byteable for PointLightUniform {}
39-
4038
impl PointLightUniform {
4139
pub fn new(light: &PointLight, global_transform: &GlobalTransform) -> PointLightUniform {
4240
let (x, y, z) = global_transform.translation.into();
@@ -118,14 +116,12 @@ impl Default for DirectionalLight {
118116
}
119117

120118
#[repr(C)]
121-
#[derive(Debug, Clone, Copy)]
119+
#[derive(Debug, Clone, Copy, Pod, Zeroable)]
122120
pub(crate) struct DirectionalLightUniform {
123121
pub dir: [f32; 4],
124122
pub color: [f32; 4],
125123
}
126124

127-
unsafe impl Byteable for DirectionalLightUniform {}
128-
129125
impl DirectionalLightUniform {
130126
pub fn new(light: &DirectionalLight) -> DirectionalLightUniform {
131127
// direction is negated to be ready for N.L

crates/bevy_pbr/src/render_graph/lights_node.rs

Lines changed: 14 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ use crate::{
44
},
55
render_graph::uniform,
66
};
7-
use bevy_core::{AsBytes, Byteable};
7+
use bevy_core::{bytes_of, Pod, Zeroable};
88
use bevy_ecs::{
99
system::{BoxedSystem, IntoSystem, Local, Query, Res, ResMut},
1010
world::World,
@@ -49,16 +49,14 @@ impl Node for LightsNode {
4949
}
5050

5151
#[repr(C)]
52-
#[derive(Debug, Clone, Copy)]
52+
#[derive(Debug, Clone, Copy, Pod, Zeroable)]
5353
struct LightCount {
5454
// storing as a `[u32; 4]` for memory alignement
5555
// Index 0 is for point lights,
5656
// Index 1 is for directional lights
5757
pub num_lights: [u32; 4],
5858
}
5959

60-
unsafe impl Byteable for LightCount {}
61-
6260
impl SystemNode for LightsNode {
6361
fn get_system(&self) -> BoxedSystem {
6462
let system = lights_node_system.system().config(|config| {
@@ -160,29 +158,33 @@ pub fn lights_node_system(
160158
0..max_light_uniform_size as u64,
161159
&mut |data, _renderer| {
162160
// ambient light
163-
data[0..ambient_light_size].copy_from_slice(ambient_light.as_bytes());
161+
data[0..ambient_light_size].copy_from_slice(bytes_of(&ambient_light));
164162

165163
// light count
166-
data[ambient_light_size..light_count_size].copy_from_slice(
167-
[point_light_count as u32, dir_light_count as u32, 0, 0].as_bytes(),
168-
);
164+
data[ambient_light_size..light_count_size].copy_from_slice(bytes_of(&[
165+
point_light_count as u32,
166+
dir_light_count as u32,
167+
0,
168+
0,
169+
]));
169170

170171
// point light array
171172
for ((point_light, global_transform), slot) in point_lights.iter().zip(
172173
data[point_light_uniform_start..point_light_uniform_end]
173174
.chunks_exact_mut(point_light_size),
174175
) {
175-
slot.copy_from_slice(
176-
PointLightUniform::new(&point_light, &global_transform).as_bytes(),
177-
);
176+
slot.copy_from_slice(bytes_of(&PointLightUniform::new(
177+
&point_light,
178+
&global_transform,
179+
)));
178180
}
179181

180182
// directional light array
181183
for (dir_light, slot) in dir_lights.iter().zip(
182184
data[dir_light_uniform_start..dir_light_uniform_end]
183185
.chunks_exact_mut(dir_light_size),
184186
) {
185-
slot.copy_from_slice(DirectionalLightUniform::new(&dir_light).as_bytes());
187+
slot.copy_from_slice(bytes_of(&DirectionalLightUniform::new(&dir_light)));
186188
}
187189
},
188190
);

0 commit comments

Comments
 (0)