Skip to content

Commit 70b1da7

Browse files
committed
shaders: add trait BufferStruct and derive macro
1 parent 42a5679 commit 70b1da7

File tree

16 files changed

+631
-26
lines changed

16 files changed

+631
-26
lines changed

Cargo.lock

Lines changed: 4 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 3 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@ graphite-proc-macros = { path = "proc-macros" }
8484

8585
# Workspace dependencies
8686
rustc-hash = "2.0"
87-
bytemuck = { version = "1.13", features = ["derive"] }
87+
bytemuck = { version = "1.13", features = ["derive", "min_const_generics"] }
8888
serde = { version = "1.0", features = ["derive", "rc"] }
8989
serde_json = "1.0"
9090
serde-wasm-bindgen = "0.6"
@@ -154,7 +154,7 @@ parley = "0.5.0"
154154
skrifa = "0.32.0"
155155
pretty_assertions = "1.4.1"
156156
fern = { version = "0.7", features = ["colored"] }
157-
num_enum = "0.7"
157+
num_enum = { version = "0.7", default-features = false }
158158
num-derive = "0.4"
159159
num-traits = { version = "0.2", default-features = false, features = ["libm"] }
160160
specta = { version = "2.0.0-rc.22", features = [
@@ -192,18 +192,12 @@ tracing = "0.1.41"
192192
rfd = "0.15.4"
193193
open = "5.3.2"
194194
poly-cool = "0.2.0"
195-
spirv-std = { git = "https://github.com/rust-gpu/rust-gpu", rev = "c12f216121820580731440ee79ebc7403d6ea04f" }
195+
spirv-std = { git = "https://github.com/rust-gpu/rust-gpu", rev = "c12f216121820580731440ee79ebc7403d6ea04f", features = ["bytemuck"] }
196196
cargo-gpu = { git = "https://github.com/rust-gpu/cargo-gpu", rev = "f969528e87baa17a7d48eecf4a6fcfdcaaf30566" }
197197

198198
[workspace.lints.rust]
199199
unexpected_cfgs = { level = "allow", check-cfg = ['cfg(target_arch, values("spirv"))'] }
200200

201-
[workspace.lints.rust]
202-
unexpected_cfgs = { level = "allow", check-cfg = ['cfg(target_arch, values("spirv"))'] }
203-
204-
[workspace.lints.rust]
205-
unexpected_cfgs = { level = "allow", check-cfg = ['cfg(target_arch, values("spirv"))'] }
206-
207201
[profile.dev]
208202
opt-level = 1
209203

node-graph/gcore-shaders/Cargo.toml

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,10 +22,14 @@ std = [
2222
"glam/serde",
2323
"half/std",
2424
"half/serde",
25-
"num-traits/std"
25+
"num-traits/std",
26+
"num_enum/std",
2627
]
2728

2829
[dependencies]
30+
# Local dependencies
31+
node-macro = { workspace = true }
32+
2933
# Local std dependencies
3034
dyn-any = { workspace = true, optional = true }
3135

@@ -35,6 +39,7 @@ glam = { workspace = true }
3539
half = { workspace = true, default-features = false }
3640
num-derive = { workspace = true }
3741
num-traits = { workspace = true }
42+
num_enum = { workspace = true }
3843
spirv-std = { workspace = true }
3944

4045
# Workspace std dependencies

node-graph/gcore-shaders/src/blending.rs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
use core::fmt::Display;
22
use core::hash::{Hash, Hasher};
3+
use node_macro::BufferStruct;
4+
use num_enum::{FromPrimitive, IntoPrimitive};
35
#[cfg(not(feature = "std"))]
46
use num_traits::float::Float;
57

6-
#[derive(Debug, Clone, Copy, PartialEq)]
8+
#[derive(Debug, Clone, Copy, PartialEq, BufferStruct)]
79
#[cfg_attr(feature = "std", derive(dyn_any::DynAny, specta::Type, serde::Serialize, serde::Deserialize))]
810
#[cfg_attr(feature = "std", serde(default))]
911
pub struct AlphaBlending {
@@ -66,7 +68,7 @@ impl AlphaBlending {
6668
}
6769

6870
#[repr(i32)]
69-
#[derive(Debug, Default, Clone, Copy, Eq, PartialEq, Hash, bytemuck::NoUninit)]
71+
#[derive(Debug, Default, Clone, Copy, Eq, PartialEq, Hash, BufferStruct, FromPrimitive, IntoPrimitive)]
7072
#[cfg_attr(feature = "std", derive(dyn_any::DynAny, specta::Type, serde::Serialize, serde::Deserialize))]
7173
pub enum BlendMode {
7274
// Basic group

node-graph/gcore-shaders/src/color/color_types.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ use core::fmt::Debug;
55
use core::hash::Hash;
66
use glam::Vec4;
77
use half::f16;
8+
use node_macro::BufferStruct;
89
#[cfg(not(feature = "std"))]
910
use num_traits::Euclid;
1011
#[cfg(not(feature = "std"))]
@@ -215,7 +216,7 @@ impl Pixel for Luma {}
215216
/// The other components (RGB) are stored as `f32` that range from `0.0` up to `f32::MAX`,
216217
/// the values encode the brightness of each channel proportional to the light intensity in cd/m² (nits) in HDR, and `0.0` (black) to `1.0` (white) in SDR color.
217218
#[repr(C)]
218-
#[derive(Debug, Default, Clone, Copy, PartialEq, Pod, Zeroable)]
219+
#[derive(Debug, Default, Clone, Copy, PartialEq, Pod, Zeroable, BufferStruct)]
219220
#[cfg_attr(feature = "std", derive(dyn_any::DynAny, specta::Type, serde::Serialize, serde::Deserialize))]
220221
pub struct Color {
221222
red: f32,
Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
use crate::shaders::buffer_struct::BufferStruct;
2+
3+
macro_rules! glam_array {
4+
($t:ty, $a:ty) => {
5+
unsafe impl BufferStruct for $t {
6+
type Buffer = $a;
7+
8+
#[inline]
9+
fn write(from: Self) -> Self::Buffer {
10+
<$t>::to_array(&from)
11+
}
12+
13+
#[inline]
14+
fn read(from: Self::Buffer) -> Self {
15+
<$t>::from_array(from)
16+
}
17+
}
18+
};
19+
}
20+
21+
macro_rules! glam_cols_array {
22+
($t:ty, $a:ty) => {
23+
unsafe impl BufferStruct for $t {
24+
type Buffer = $a;
25+
26+
#[inline]
27+
fn write(from: Self) -> Self::Buffer {
28+
<$t>::to_cols_array(&from)
29+
}
30+
31+
#[inline]
32+
fn read(from: Self::Buffer) -> Self {
33+
<$t>::from_cols_array(&from)
34+
}
35+
}
36+
};
37+
}
38+
39+
glam_array!(glam::Vec2, [f32; 2]);
40+
glam_array!(glam::Vec3, [f32; 3]);
41+
// glam_array!(Vec3A, [f32; 4]);
42+
glam_array!(glam::Vec4, [f32; 4]);
43+
glam_array!(glam::Quat, [f32; 4]);
44+
glam_cols_array!(glam::Mat2, [f32; 4]);
45+
glam_cols_array!(glam::Mat3, [f32; 9]);
46+
// glam_cols_array!(Mat3A, [f32; 4]);
47+
glam_cols_array!(glam::Mat4, [f32; 16]);
48+
glam_cols_array!(glam::Affine2, [f32; 6]);
49+
glam_cols_array!(glam::Affine3A, [f32; 12]);
50+
51+
glam_array!(glam::DVec2, [f64; 2]);
52+
glam_array!(glam::DVec3, [f64; 3]);
53+
glam_array!(glam::DVec4, [f64; 4]);
54+
glam_array!(glam::DQuat, [f64; 4]);
55+
glam_cols_array!(glam::DMat2, [f64; 4]);
56+
glam_cols_array!(glam::DMat3, [f64; 9]);
57+
glam_cols_array!(glam::DMat4, [f64; 16]);
58+
glam_cols_array!(glam::DAffine2, [f64; 6]);
59+
glam_cols_array!(glam::DAffine3, [f64; 12]);
60+
61+
glam_array!(glam::I16Vec2, [i16; 2]);
62+
glam_array!(glam::I16Vec3, [i16; 3]);
63+
glam_array!(glam::I16Vec4, [i16; 4]);
64+
65+
glam_array!(glam::U16Vec2, [u16; 2]);
66+
glam_array!(glam::U16Vec3, [u16; 3]);
67+
glam_array!(glam::U16Vec4, [u16; 4]);
68+
69+
glam_array!(glam::IVec2, [i32; 2]);
70+
glam_array!(glam::IVec3, [i32; 3]);
71+
glam_array!(glam::IVec4, [i32; 4]);
72+
73+
glam_array!(glam::UVec2, [u32; 2]);
74+
glam_array!(glam::UVec3, [u32; 3]);
75+
glam_array!(glam::UVec4, [u32; 4]);
76+
77+
glam_array!(glam::I64Vec2, [i64; 2]);
78+
glam_array!(glam::I64Vec3, [i64; 3]);
79+
glam_array!(glam::I64Vec4, [i64; 4]);
80+
81+
glam_array!(glam::U64Vec2, [u64; 2]);
82+
glam_array!(glam::U64Vec3, [u64; 3]);
83+
glam_array!(glam::U64Vec4, [u64; 4]);
84+
85+
unsafe impl BufferStruct for glam::Vec3A {
86+
type Buffer = [f32; 4];
87+
88+
#[inline]
89+
fn write(from: Self) -> Self::Buffer {
90+
glam::Vec4::to_array(&from.extend(0.))
91+
}
92+
93+
#[inline]
94+
fn read(from: Self::Buffer) -> Self {
95+
glam::Vec3A::from_vec4(glam::Vec4::from_array(from))
96+
}
97+
}
98+
99+
/// do NOT use slices, otherwise spirv will fail to compile
100+
unsafe impl BufferStruct for glam::Mat3A {
101+
type Buffer = [f32; 12];
102+
103+
#[inline]
104+
fn write(from: Self) -> Self::Buffer {
105+
let a = from.to_cols_array();
106+
[a[0], a[1], a[2], a[3], a[4], a[5], a[6], a[7], a[8], 0., 0., 0.]
107+
}
108+
109+
#[inline]
110+
fn read(from: Self::Buffer) -> Self {
111+
let a = from;
112+
glam::Mat3A::from_cols_array(&[a[0], a[1], a[2], a[3], a[4], a[5], a[6], a[7], a[8]])
113+
}
114+
}
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
//! I (@firestar99) copied this entire mod from one of my projects, as I haven't uploaded that lib to crates. Hopefully
2+
//! rust-gpu improves and this entire thing becomes unnecessary in the future.
3+
//!
4+
//! https://github.com/Firestar99/nanite-at-home/tree/008dac8df656959c71efeddd2d3ddabcb801771c/rust-gpu-bindless/crates/buffer-content
5+
6+
use bytemuck::Pod;
7+
8+
mod glam;
9+
mod primitive;
10+
11+
/// A BufferStruct is a "parallel representation" of the original struct with some fundamental types remapped. This
12+
/// struct hierarchy represents how data is stored in GPU Buffers, where all types must be [`Pod`] to allow
13+
/// transmuting them to `&[u8]` with [`bytemuck`].
14+
///
15+
/// Notable type remappings (original: buffer):
16+
/// * bool: u32 of 0 or 1
17+
/// * any repr(u32) enum: u32 with remapping via [`num_enum`]
18+
///
19+
/// By adding `#[derive(ShaderStruct)]` to your struct (or enum), a parallel `{name}Buffer` struct is created with all
20+
/// the members of the original struct, but with their types using the associated remapped types as specified by this
21+
/// trait.
22+
///
23+
/// # Origin
24+
/// I (@firestar99) copied this entire mod from my [Nanite-at-home] project, specifically the [buffer-content] crate
25+
/// and the [buffer_struct] proc macro. The variant here has quite some modifications, to both cleaned up some of the
26+
/// mistakes my implementation has and to customize it a bit for graphite.
27+
///
28+
/// Hopefully rust-gpu improves to the point where this remapping becomes unnecessary.
29+
///
30+
/// [Nanite-at-home]: https://github.com/Firestar99/nanite-at-home
31+
/// [buffer-content]: https://github.com/Firestar99/nanite-at-home/tree/008dac8df656959c71efeddd2d3ddabcb801771c/rust-gpu-bindless/crates/buffer-content
32+
/// [buffer_struct]: https://github.com/Firestar99/nanite-at-home/blob/008dac8df656959c71efeddd2d3ddabcb801771c/rust-gpu-bindless/crates/macros/src/buffer_struct.rs
33+
///
34+
/// # Safety
35+
/// The associated type Transfer must be the same on all targets. Writing followed by reading back a value must result
36+
/// in the same value.
37+
pub unsafe trait BufferStruct: Copy + Send + Sync + 'static {
38+
type Buffer: Pod + Send + Sync;
39+
40+
fn write(from: Self) -> Self::Buffer;
41+
42+
fn read(from: Self::Buffer) -> Self;
43+
}
44+
45+
/// Trait marking all [`BufferStruct`] whose read and write methods are identity. While [`BufferStruct`] only
46+
/// requires `t == read(write(t))`, this trait additionally requires `t == read(t) == write(t)`. As this removes the
47+
/// conversion requirement for writing to or reading from a buffer, one can acquire slices from buffers created of these
48+
/// types.
49+
///
50+
/// Implementing this type is completely safe due to the [`Pod`] requirement.
51+
pub trait BufferStructIdentity: Pod + Send + Sync {}
52+
53+
unsafe impl<T: BufferStructIdentity> BufferStruct for T {
54+
type Buffer = Self;
55+
56+
fn write(from: Self) -> Self::Buffer {
57+
from
58+
}
59+
60+
fn read(from: Self::Buffer) -> Self {
61+
from
62+
}
63+
}

0 commit comments

Comments
 (0)