Skip to main content

const_shader_layout/
compat.rs

1use crate::ShaderLayout;
2
3/// Marks the type's uniform-compatible alignment requirement in shader, i.e. with uniform address layout constraints.
4///
5/// Use [`crate::shader_layout_compat`] to implement this.
6/// Use [`crate::impl_shader_layout_compat_array_element`] to manually implement [`ShaderLayoutCompatArrayElement`] if needed.
7///
8/// See also <https://www.w3.org/TR/WGSL/#alignment-and-size> and <https://www.w3.org/TR/WGSL/#address-space-layout-constraints>
9pub trait ShaderLayoutCompat: ShaderLayout {
10    /// The type's alignment constraint in shader if uniform address layout constraints apply when the type is in an array or struct.
11    /// If not constrained, this should be [`ShaderLayout::ALIGN`].
12    const ALIGN_CONSTRAINT: core::num::NonZero<u64> = Self::ALIGN;
13    /// The type's size constraint in shader if uniform address layout constraints apply when the type is in an array or struct.
14    /// If not constrained, this should be `size_of::<Self>()`.
15    ///
16    /// This is used to determined the required offset after this field in struct and is not the actual size of type.
17    const SIZE_CONSTRAINT: core::num::NonZero<u64> =
18        core::num::NonZero::new(size_of::<Self>() as u64).unwrap();
19}
20
21/// Marks the type can be used as array element with uniform address layout constraints.
22///
23/// Note: Use [`crate::impl_shader_layout_compat_array_element`] to manually implement this if needed.
24/// [`crate::shader_layout_compat`] doesn't automatically implement this.
25///
26/// There is a blanket implementation of `ShaderLayoutCompat` for `[T; N]` where `T: ShaderLayoutCompatArrayElement`.
27pub trait ShaderLayoutCompatArrayElement: crate::ShaderLayoutArrayElement {}
28
29/// Implements [`ShaderLayoutCompat`] (also implements [`ShaderLayout`]) for the types, with the specified alignment.
30#[macro_export]
31#[doc(hidden)]
32macro_rules! impl_shader_layout_compat {
33    ($($ty:ty),+$(,)?) => {
34        $(
35            $crate::impl_shader_layout!($ty);
36            impl $crate::ShaderLayoutCompat for $ty {}
37        )+
38    };
39    ($align:expr $(, $ty:ty)+$(,)?) => {
40        $(
41            $crate::impl_shader_layout!($align, $ty);
42            impl $crate::ShaderLayoutCompat for $ty {}
43        )+
44    };
45    ($align:expr, $align_constraint:expr $(, $ty:ty)+$(,)?) => {
46        $(
47            $crate::impl_shader_layout!($align, $ty);
48            impl $crate::ShaderLayoutCompat for $ty {
49                const ALIGN_CONSTRAINT: ::core::num::NonZero<u64> = ::core::num::NonZero::new($align_constraint).unwrap();
50            }
51        )+
52    };
53    ($align:expr, $align_constraint:expr, $size_constraint:expr $(, $ty:ty)+$(,)?) => {
54        const _: () ={
55            assert!((($size_constraint) as u64).is_multiple_of(16u64));
56        };
57        $(
58            $crate::impl_shader_layout!($align, $ty);
59            impl $crate::ShaderLayoutCompat for $ty {
60                const ALIGN_CONSTRAINT: ::core::num::NonZero<u64> = ::core::num::NonZero::new($align_constraint).unwrap();
61                const SIZE_CONSTRAINT: ::core::num::NonZero<u64> = ::core::num::NonZero::new($size_constraint).unwrap();
62            }
63        )+
64    };
65}
66
67#[macro_export]
68#[doc(hidden)]
69macro_rules! primitive_impl_shader_layout_compat_array_element {
70    ($($ty:ty),+$(,)?) => {
71        $(
72            $crate::impl_shader_layout_array_element!($ty);
73            $crate::impl_shader_layout_compat_array_element!($ty);
74        )+
75    };
76}
77
78/// Implements [`ShaderLayoutCompat`] (also implements [`ShaderLayout`]) for `[T; N]` for types implemented [`ShaderLayoutCompat`].
79///
80/// Different from [`ShaderLayout`], the stride of array must be a multiple of 16.
81///
82/// Checks at compile-time:
83/// * Array size must be equal to `N * roundUp(16, roundUp(AlignOf(E), SizeOf(E)))`.
84///
85/// See also <https://www.w3.org/TR/WGSL/#alignment-and-size> and <https://www.w3.org/TR/WGSL/#address-space-layout-constraints>
86#[macro_export]
87macro_rules! impl_shader_layout_compat_array_element {
88    ($($ty:ty),+$(,)?) => {
89        $(
90            impl $crate::ShaderLayoutCompatArrayElement for $ty {}
91
92            // Assert array size is equal to `N * roundUp(16, roundUp(AlignOf(E), SizeOf(E)))`
93            const _: () = {
94                const ELEMENT_ALIGN: u64 = <$ty as $crate::ShaderLayout>::ALIGN.get().next_multiple_of(16);
95                const N: usize = 1;
96                const ACTUAL_SIZE: u64 = size_of::<[$ty; N]>() as u64;
97                const SIZE: u64 = (size_of::<$ty>() as u64).next_multiple_of(ELEMENT_ALIGN) * N as u64;
98                if SIZE != ACTUAL_SIZE {
99                    let mut msg = $crate::internal::MsgBuf::<256>::new();
100                    msg.write_str("Failed to implement `ShaderLayoutCompatArrayElement`: `[")
101                        .write_str(stringify!($ty))
102                        .write_str("; N]` size (")
103                        .write_usize(size_of::<[$ty; N]>())
104                        .write_str(" * N) must be equal to its shader size (")
105                        .write_u64(SIZE)
106                        .write_str(" * N), i.e. the stride must be rounded up to `ALIGN` (")
107                        .write_u64(ELEMENT_ALIGN)
108                        .write_str(") and 16");
109                    panic!("{}", msg.as_str());
110                }
111            };
112        )+
113    };
114}
115
116#[macro_export]
117macro_rules! impl_shader_layout_compat_struct {
118    (
119        $(#[$attr:meta])*
120        $vis:vis struct $struct_name:ident {
121            $(
122                $(#[$field_attr:meta])*
123                $field_vis:vis $field_name:ident : $field_ty:ty
124            ),* $(,)?
125        }
126   ) => {
127       $crate::impl_shader_layout_struct!(
128           $(#[$attr])*
129           $vis struct $struct_name {
130               $(
131                   $(#[$field_attr])*
132                   $field_vis $field_name: $field_ty
133               ),*
134           }
135       );
136
137        $(
138            const _: () = {
139                const MEMBER_ALIGN_CONSTRAINT: u64 = <$field_ty as $crate::ShaderLayoutCompat>::ALIGN_CONSTRAINT.get();
140                const MEMBER_SIZE_CONSTRAINT: u64 = <$field_ty as $crate::ShaderLayoutCompat>::SIZE_CONSTRAINT.get();
141                const MEMBER_SIZE: u64 = size_of::<$field_ty>() as u64;
142                const MEMBER_OFFSET: u64 = core::mem::offset_of!($struct_name, $field_name) as u64;
143
144                if MEMBER_SIZE != MEMBER_SIZE_CONSTRAINT {
145                    let mut msg = $crate::internal::MsgBuf::<256>::new();
146                    msg.write_str("Failed to impl `ShaderLayoutCompat`: field `")
147                        .write_str(stringify!($struct_name))
148                        .write_str("::")
149                        .write_str(stringify!($field_name))
150                        .write_str("` (`")
151                        .write_str(stringify!($field_ty))
152                        .write_str("`) size (")
153                        .write_u64(MEMBER_SIZE)
154                        .write_str(") must be `SIZE_CONSTRAINT` (")
155                        .write_u64(MEMBER_SIZE_CONSTRAINT)
156                        .write_str(") due to uniform layout constraints");
157                    panic!("{}", msg.as_str());
158                }
159
160                if !MEMBER_OFFSET.is_multiple_of(MEMBER_ALIGN_CONSTRAINT) {
161                    let mut msg = $crate::internal::MsgBuf::<256>::new();
162                    msg.write_str("Failed to impl `ShaderLayoutCompat`: field `")
163                        .write_str(stringify!($struct_name))
164                        .write_str("::")
165                        .write_str(stringify!($field_name))
166                        .write_str("` (`")
167                        .write_str(stringify!($field_ty))
168                        .write_str("`) is not properly aligned. The offset is ")
169                        .write_u64(MEMBER_OFFSET)
170                        .write_str(" but required align is `ALIGN_CONSTRAINT` (")
171                        .write_u64(MEMBER_ALIGN_CONSTRAINT)
172                        .write_str(")");
173                    panic!("{}", msg.as_str());
174                }
175            };
176        )*
177
178        impl $crate::ShaderLayoutCompat for $struct_name {
179            const ALIGN_CONSTRAINT: ::core::num::NonZero<u64> = {
180                const MEMBER_ALIGNS: &[u64] = &[$(
181                    (<$field_ty as $crate::ShaderLayoutCompat>::ALIGN_CONSTRAINT.get())
182                ),*];
183
184                let mut max = MEMBER_ALIGNS[0];
185                let mut i = 1;
186                while i < MEMBER_ALIGNS.len() {
187                    if MEMBER_ALIGNS[i] > max {
188                        max = MEMBER_ALIGNS[i];
189                    }
190                    i += 1;
191                }
192                ::core::num::NonZero::new(max.next_multiple_of(16)).unwrap()
193            };
194            const SIZE_CONSTRAINT: ::core::num::NonZero<u64> = {
195                const MEMBER_SIZES: &[u64] = &[$(
196                    (<$field_ty as $crate::ShaderLayoutCompat>::SIZE_CONSTRAINT.get())
197                ),*];
198
199                let mut sum = MEMBER_SIZES[0];
200                let mut i = 1;
201                while i < MEMBER_SIZES.len() {
202                    sum += MEMBER_SIZES[i];
203                    i += 1;
204                }
205                ::core::num::NonZero::new(sum.next_multiple_of(16)).unwrap()
206            };
207        }
208    };
209}
210
211/// Checks if all the struct's fields conform to shader layout then implements [`ShaderLayoutCompat`] for this struct, or fails at compile-time.
212///
213/// Different from [`ShaderLayout`], the alignment of struct must be a multiple of 16.
214///
215/// Checks at compile-time:
216/// * For each field, `core::mem::offset_of!(struct, field)` must be equal to its [`ShaderLayoutCompat::ALIGN_CONSTRAINT`].
217/// * Struct size must be equal to `roundUp(16, roundUp(AlignOf(S), SizeOf(S))))`.
218///
219/// See also <https://www.w3.org/TR/WGSL/#alignment-and-size> and <https://www.w3.org/TR/WGSL/#address-space-layout-constraints>
220#[macro_export]
221macro_rules! shader_layout_compat {
222    (
223        $(#[$attr:meta])*
224        $vis:vis struct $struct_name:ident {
225            $(
226                $(#[$field_attr:meta])*
227                $field_vis:vis $field_name:ident : $field_ty:ty
228            ),* $(,)?
229        }
230   ) => {
231       #[derive(Copy, Clone)]
232       #[repr(C)]
233       $(#[$attr])*
234       $vis struct $struct_name {
235           $(
236               $(#[$field_attr])*
237               $field_vis $field_name: $field_ty
238           ),*
239       }
240
241       $crate::impl_shader_layout_compat_struct!{
242           $(#[$attr])*
243           $vis struct $struct_name {
244               $(
245                   $(#[$field_attr])*
246                   $field_vis $field_name: $field_ty
247               ),*
248           }
249       }
250    };
251}