fyrox_graphics/
uniform.rs

1// Copyright (c) 2019-present Dmitry Stepanov and Fyrox Engine contributors.
2//
3// Permission is hereby granted, free of charge, to any person obtaining a copy
4// of this software and associated documentation files (the "Software"), to deal
5// in the Software without restriction, including without limitation the rights
6// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7// copies of the Software, and to permit persons to whom the Software is
8// furnished to do so, subject to the following conditions:
9//
10// The above copyright notice and this permission notice shall be included in all
11// copies or substantial portions of the Software.
12//
13// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19// SOFTWARE.
20
21#![warn(missing_docs)]
22
23//! Uniform buffer is a special byte storage that ensures correct data alignment suitable for GPU.
24//! Current implementation supports `std140` data layout scheme.
25
26use crate::core::{
27    algebra::{Matrix2, Matrix3, Matrix4, Vector2, Vector3, Vector4},
28    array_as_u8_slice,
29    arrayvec::ArrayVec,
30    color::Color,
31    value_as_u8_slice,
32};
33
34/// A trait for any storage suitable to store bytes for uniforms.
35pub trait ByteStorage: Default {
36    /// Clears the storage.
37    fn reset(&mut self);
38    /// Returns a reference to the internal bytes array.
39    fn bytes(&self) -> &[u8];
40    /// Returns total number of bytes that is currently in the storage.
41    fn bytes_count(&self) -> usize;
42    /// Writes the given number of bytes to the storage.
43    fn write_bytes(&mut self, bytes: &[u8]);
44    /// Writes the given number of zero bytes to the storage.
45    fn write_zeros(&mut self, count: usize);
46}
47
48impl<const N: usize> ByteStorage for ArrayVec<u8, N> {
49    fn reset(&mut self) {
50        self.clear();
51    }
52
53    fn bytes(&self) -> &[u8] {
54        self.as_slice()
55    }
56
57    fn bytes_count(&self) -> usize {
58        self.len()
59    }
60
61    fn write_bytes(&mut self, bytes: &[u8]) {
62        self.try_extend_from_slice(bytes).unwrap()
63    }
64
65    fn write_zeros(&mut self, count: usize) {
66        let old_len = self.len();
67        let new_len = old_len + count;
68        assert!(new_len <= self.capacity());
69        // SAFETY: Out-of-bounds writes prevented by the above assert.
70        unsafe {
71            self.set_len(new_len);
72            std::ptr::write_bytes(self.as_mut_ptr().add(old_len), 0, count)
73        }
74    }
75}
76
77impl ByteStorage for Vec<u8> {
78    fn reset(&mut self) {
79        self.clear();
80    }
81
82    fn bytes(&self) -> &[u8] {
83        self.as_slice()
84    }
85
86    fn bytes_count(&self) -> usize {
87        self.len()
88    }
89
90    fn write_bytes(&mut self, bytes: &[u8]) {
91        self.extend_from_slice(bytes)
92    }
93
94    // clippy is not smart enough here. In reality there's no uninitialized content in the vec,
95    // because we do `write_bytes` right after.
96    #[allow(clippy::uninit_vec)]
97    fn write_zeros(&mut self, count: usize) {
98        let old_len = self.len();
99        let new_len = old_len + count;
100        self.reserve(count);
101        // SAFETY: Out-of-bounds writes prevented by the `reserve` call.
102        unsafe {
103            self.set_len(new_len);
104            std::ptr::write_bytes(self.as_mut_ptr().add(old_len), 0, count)
105        }
106    }
107}
108
109/// Uniform buffer is a special byte storage that ensures correct data alignment suitable for GPU.
110/// Current implementation supports `std140` data layout scheme.
111///
112/// ## Examples
113///
114/// ```rust
115/// # use fyrox_core::{
116/// #     algebra::{Matrix4, Vector3},
117/// #     color::Color
118/// # };
119/// # use fyrox_graphics::uniform::StaticUniformBuffer;
120/// let bytes = StaticUniformBuffer::<256>::new()
121///     .with(&Matrix4::identity())
122///     .with(&Color::WHITE)
123///     .with(&Vector3::new(0.0, 1.0, 0.0))
124///     .finish();
125/// ```
126#[derive(Default)]
127pub struct UniformBuffer<S: ByteStorage> {
128    storage: S,
129}
130
131/// A uniform buffer backed by an array of fixed size.
132pub type StaticUniformBuffer<const N: usize> = UniformBuffer<ArrayVec<u8, N>>;
133
134/// A uniform buffer backed by a dynamic array.
135pub type DynamicUniformBuffer = UniformBuffer<Vec<u8>>;
136
137/// A trait for entities that supports `std140` data layout.
138pub trait Std140 {
139    /// Alignment in bytes.
140    const ALIGNMENT: usize;
141
142    /// Writes self to the given bytes storage.
143    fn write<T: ByteStorage>(&self, dest: &mut T);
144}
145
146macro_rules! default_write_impl {
147    () => {
148        fn write<T: ByteStorage>(&self, dest: &mut T) {
149            dest.write_bytes(value_as_u8_slice(self))
150        }
151    };
152}
153
154impl Std140 for f32 {
155    const ALIGNMENT: usize = 4;
156    default_write_impl!();
157}
158
159impl Std140 for u32 {
160    const ALIGNMENT: usize = 4;
161    default_write_impl!();
162}
163
164impl Std140 for i32 {
165    const ALIGNMENT: usize = 4;
166    default_write_impl!();
167}
168
169impl Std140 for Vector2<f32> {
170    const ALIGNMENT: usize = 8;
171    default_write_impl!();
172}
173
174impl Std140 for Vector3<f32> {
175    const ALIGNMENT: usize = 16;
176    default_write_impl!();
177}
178
179impl Std140 for Vector4<f32> {
180    const ALIGNMENT: usize = 16;
181    default_write_impl!();
182}
183
184impl Std140 for [f32; 2] {
185    const ALIGNMENT: usize = 8;
186    default_write_impl!();
187}
188
189impl Std140 for [f32; 3] {
190    const ALIGNMENT: usize = 16;
191
192    fn write<T: ByteStorage>(&self, dest: &mut T) {
193        dest.write_bytes(value_as_u8_slice(self));
194        dest.write_bytes(&[0; size_of::<f32>()]);
195    }
196}
197
198impl Std140 for Matrix4<f32> {
199    const ALIGNMENT: usize = 16;
200    default_write_impl!();
201}
202
203impl Std140 for Matrix3<f32> {
204    const ALIGNMENT: usize = 16;
205
206    fn write<T: ByteStorage>(&self, dest: &mut T) {
207        for row in (self as &dyn AsRef<[[f32; 3]; 3]>).as_ref() {
208            dest.write_bytes(array_as_u8_slice(row));
209            dest.write_bytes(&[0; size_of::<f32>()]);
210        }
211    }
212}
213
214impl Std140 for Matrix2<f32> {
215    const ALIGNMENT: usize = 16;
216
217    fn write<T: ByteStorage>(&self, dest: &mut T) {
218        for row in (self as &dyn AsRef<[[f32; 2]; 2]>).as_ref() {
219            dest.write_bytes(array_as_u8_slice(row));
220            dest.write_bytes(&[0; 2 * size_of::<f32>()]);
221        }
222    }
223}
224
225impl Std140 for [f32; 4] {
226    const ALIGNMENT: usize = 16;
227    default_write_impl!();
228}
229
230impl Std140 for Color {
231    const ALIGNMENT: usize = 16;
232
233    fn write<T: ByteStorage>(&self, dest: &mut T) {
234        let frgba = self.as_frgba();
235        dest.write_bytes(value_as_u8_slice(&frgba));
236    }
237}
238
239impl Std140 for bool {
240    const ALIGNMENT: usize = 4;
241
242    fn write<T: ByteStorage>(&self, dest: &mut T) {
243        let integer = if *self { 1 } else { 0 };
244        dest.write_bytes(value_as_u8_slice(&integer));
245    }
246}
247
248impl<S> UniformBuffer<S>
249where
250    S: ByteStorage + Default,
251{
252    /// Creates a new uniform buffer with an empty storage.
253    pub fn new() -> Self {
254        Self {
255            storage: S::default(),
256        }
257    }
258
259    /// Creates a new uniform buffer with the given storage.
260    pub fn with_storage(storage: S) -> Self {
261        Self { storage }
262    }
263
264    /// Clears the uniform buffer.
265    pub fn clear(&mut self) {
266        self.storage.reset();
267    }
268
269    /// Returns total number of bytes stored in the uniform buffer. Keep in mind, that the number
270    /// in the vast majority of cases won't match the sum of all pushed elements due to alignment
271    /// requirements.
272    pub fn len(&self) -> usize {
273        self.storage.bytes_count()
274    }
275
276    /// Checks if the buffer is empty or not.
277    pub fn is_empty(&self) -> bool {
278        self.len() == 0
279    }
280
281    /// Pushes the given amount of padding bytes to the storage.
282    pub fn push_padding(&mut self, alignment: usize) {
283        debug_assert!(alignment.is_power_of_two());
284        let bytes_count = self.storage.bytes_count();
285        let remainder = (alignment - 1) & bytes_count;
286        if remainder > 0 {
287            let padding = alignment - remainder;
288            self.storage.write_zeros(padding);
289        }
290    }
291
292    /// Pushes a value to the storage. This method ensures that the correct alignment for the pushed
293    /// value is preserved.
294    pub fn push<T>(&mut self, value: &T) -> &mut Self
295    where
296        T: Std140,
297    {
298        self.push_padding(T::ALIGNMENT);
299        value.write(&mut self.storage);
300        self
301    }
302
303    /// The same as [`Self::push`], but allows chained calls in a builder manner.
304    pub fn with<T>(mut self, value: &T) -> Self
305    where
306        T: Std140,
307    {
308        self.push(value);
309        self
310    }
311
312    fn push_array_element<T: Std140>(&mut self, item: &T) {
313        self.push_padding(16);
314        item.write(&mut self.storage);
315        self.push_padding(16);
316    }
317
318    /// Pushes a slice of given values. Keep in mind, that this method is not the same as pushing
319    /// individual slice elements one by one. Instead, this method preserves alignment requirements
320    /// for arrays as `std140` rule set requires.
321    pub fn push_slice<T>(&mut self, slice: &[T]) -> &mut Self
322    where
323        T: Std140,
324    {
325        for item in slice {
326            self.push_array_element(item);
327        }
328        self
329    }
330
331    /// Pushes the given slice into the uniform buffer and pads the rest of the space
332    /// (`max_len - slice_len`) with the default value of the underlying type.
333    pub fn push_slice_with_max_size<T: Std140 + Default>(
334        &mut self,
335        slice: &[T],
336        max_len: usize,
337    ) -> &mut Self {
338        let len = slice.len();
339        if !slice.is_empty() {
340            let end = max_len.min(len);
341            self.push_slice(&slice[0..end]);
342        }
343        let remainder = max_len.saturating_sub(len);
344        let item = T::default();
345        for _ in 0..remainder {
346            self.push_array_element(&item);
347        }
348        self
349    }
350
351    /// Same as [`Self::push_slice_with_max_size`], but allows changed calls with builder-like style.
352    pub fn with_slice_with_max_size<T: Std140 + Default>(
353        mut self,
354        slice: &[T],
355        max_len: usize,
356    ) -> Self {
357        self.push_slice_with_max_size(slice, max_len);
358        self
359    }
360
361    /// The same as [`Self::push_slice`], but allows chained calls in a builder manner.
362    pub fn with_slice<T>(mut self, slice: &[T]) -> Self
363    where
364        T: Std140,
365    {
366        self.push_slice(slice);
367        self
368    }
369
370    /// Returns a reference to the internal bytes storage of the uniform buffer.
371    pub fn storage(&self) -> &S {
372        &self.storage
373    }
374
375    /// Finishes buffer filling process and returns the backing storage by consuming the buffer. This
376    /// method **must** be called before sending the data GPU, otherwise the buffer may contain misaligned
377    /// data.
378    pub fn finish(mut self) -> S {
379        self.push_padding(16);
380        self.storage
381    }
382
383    /// Calculates position for the next element including the given alignment.
384    pub fn next_write_aligned_position(&self, alignment: usize) -> usize {
385        let position = self.storage.bytes_count();
386        let remainder = (alignment - 1) & position;
387        if remainder > 0 {
388            let padding = alignment - remainder;
389            position + padding
390        } else {
391            position
392        }
393    }
394
395    /// Writes bytes directly to the buffer with the given alignment. Important: this method could
396    /// be dangerous if misused, the alignment argument must be correct and comply with `std140`
397    /// data layout rules.
398    pub fn write_bytes_with_alignment(&mut self, bytes: &[u8], alignment: usize) -> usize {
399        self.push_padding(alignment);
400        let data_location = self.storage.bytes_count();
401        self.storage.write_bytes(bytes);
402        data_location
403    }
404}
405
406#[cfg(test)]
407mod test {
408    use crate::{
409        core::algebra::{Matrix3, Vector3, Vector4},
410        uniform::DynamicUniformBuffer,
411    };
412    use fyrox_core::transmute_slice;
413
414    #[test]
415    fn test_uniform_buffer() {
416        let mut buffer = DynamicUniformBuffer::default();
417        buffer.push(&123.321);
418        assert_eq!(buffer.len(), 4);
419        buffer.push(&Vector3::new(1.0, 2.0, 3.0));
420        assert_eq!(buffer.len(), 28);
421        buffer.push(&Vector4::new(1.0, 2.0, 3.0, 4.0));
422        assert_eq!(buffer.len(), 48);
423        buffer.push(&Matrix3::default());
424        assert_eq!(buffer.len(), 96);
425        buffer.push(&123.0);
426        assert_eq!(buffer.len(), 100);
427        buffer.push_slice(&[1.0, 2.0, 3.0, 4.0]);
428        assert_eq!(buffer.len(), 176);
429        let bytes = buffer.finish();
430        assert_eq!(bytes.len(), 176);
431    }
432
433    #[test]
434    fn test_uniform_buffer_mixed_alignment() {
435        let mut buffer = DynamicUniformBuffer::default();
436        buffer.push(&Vector3::repeat(1.0));
437        assert_eq!(buffer.len(), 12);
438        buffer.push(&1.0);
439        assert_eq!(buffer.len(), 16);
440    }
441
442    #[test]
443    fn test_push_with_max_len() {
444        let mut buffer = DynamicUniformBuffer::default();
445        buffer.push_slice_with_max_size(&[1.0, 2.0, 3.0, 4.0], 6);
446        let floats: &[f32] = transmute_slice(buffer.storage().as_slice());
447        assert_eq!(
448            floats,
449            &[
450                1.0, 0.0, 0.0, 0.0, // 1
451                2.0, 0.0, 0.0, 0.0, // 2
452                3.0, 0.0, 0.0, 0.0, // 3
453                4.0, 0.0, 0.0, 0.0, // 4
454                0.0, 0.0, 0.0, 0.0, // Zero with padding
455                0.0, 0.0, 0.0, 0.0, // Zero with padding
456            ]
457        );
458        buffer.clear();
459        buffer.push_slice_with_max_size(&[1.0, 2.0], 1);
460        let floats: &[f32] = transmute_slice(buffer.storage().as_slice());
461        assert_eq!(floats, &[1.0, 0.0, 0.0, 0.0,]);
462    }
463}