Skip to main content

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 {
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    /// Writes zeros to ensure that the last byte in the storage has a position that is multiple with
47    /// the given alignment. The alignment must be power of two.
48    fn push_padding(&mut self, alignment: usize) {
49        debug_assert!(alignment.is_power_of_two());
50        let bytes_count = self.bytes_count();
51        let remainder = (alignment - 1) & bytes_count;
52        if remainder > 0 {
53            let padding = alignment - remainder;
54            self.write_zeros(padding);
55        }
56    }
57}
58
59impl<const N: usize> ByteStorage for ArrayVec<u8, N> {
60    fn reset(&mut self) {
61        self.clear();
62    }
63
64    fn bytes(&self) -> &[u8] {
65        self.as_slice()
66    }
67
68    fn bytes_count(&self) -> usize {
69        self.len()
70    }
71
72    fn write_bytes(&mut self, bytes: &[u8]) {
73        self.try_extend_from_slice(bytes).unwrap()
74    }
75
76    fn write_zeros(&mut self, count: usize) {
77        let old_len = self.len();
78        let new_len = old_len + count;
79        assert!(new_len <= self.capacity());
80        // SAFETY: Out-of-bounds writes prevented by the above assert.
81        unsafe {
82            self.set_len(new_len);
83            std::ptr::write_bytes(self.as_mut_ptr().add(old_len), 0, count)
84        }
85    }
86}
87
88impl ByteStorage for Vec<u8> {
89    fn reset(&mut self) {
90        self.clear();
91    }
92
93    fn bytes(&self) -> &[u8] {
94        self.as_slice()
95    }
96
97    fn bytes_count(&self) -> usize {
98        self.len()
99    }
100
101    fn write_bytes(&mut self, bytes: &[u8]) {
102        self.extend_from_slice(bytes)
103    }
104
105    // clippy is not smart enough here. In reality there's no uninitialized content in the vec,
106    // because we do `write_bytes` right after.
107    #[allow(clippy::uninit_vec)]
108    fn write_zeros(&mut self, count: usize) {
109        let old_len = self.len();
110        let new_len = old_len + count;
111        self.reserve(count);
112        // SAFETY: Out-of-bounds writes prevented by the `reserve` call.
113        unsafe {
114            self.set_len(new_len);
115            std::ptr::write_bytes(self.as_mut_ptr().add(old_len), 0, count)
116        }
117    }
118}
119
120/// Uniform buffer is a special byte storage that ensures correct data alignment suitable for GPU.
121/// Current implementation supports `std140` data layout scheme.
122///
123/// ## Examples
124///
125/// ```rust
126/// # use fyrox_core::{
127/// #     algebra::{Matrix4, Vector3},
128/// #     color::Color
129/// # };
130/// # use fyrox_graphics::uniform::StaticUniformBuffer;
131/// let bytes = StaticUniformBuffer::<256>::new()
132///     .with(&Matrix4::identity())
133///     .with(&Color::WHITE)
134///     .with(&Vector3::new(0.0, 1.0, 0.0))
135///     .finish();
136/// ```
137#[derive(Default)]
138pub struct UniformBuffer<S: ByteStorage> {
139    storage: S,
140}
141
142/// A uniform buffer backed by an array of fixed size.
143pub type StaticUniformBuffer<const N: usize> = UniformBuffer<ArrayVec<u8, N>>;
144
145/// A uniform buffer backed by a dynamic array.
146pub type DynamicUniformBuffer = UniformBuffer<Vec<u8>>;
147
148/// A trait for entities that supports `std140` data layout.
149pub trait Std140 {
150    /// Writes self to the given bytes storage.
151    fn write(&self, dest: &mut dyn ByteStorage);
152}
153
154macro_rules! default_write_impl {
155    ($alignment:expr) => {
156        fn write(&self, dest: &mut dyn ByteStorage) {
157            dest.push_padding($alignment);
158            dest.write_bytes(value_as_u8_slice(self))
159        }
160    };
161}
162
163impl Std140 for f32 {
164    default_write_impl!(4);
165}
166
167impl Std140 for u32 {
168    default_write_impl!(4);
169}
170
171impl Std140 for i32 {
172    default_write_impl!(4);
173}
174
175impl Std140 for Vector2<f32> {
176    default_write_impl!(8);
177}
178
179impl Std140 for Vector3<f32> {
180    default_write_impl!(16);
181}
182
183impl Std140 for Vector4<f32> {
184    default_write_impl!(16);
185}
186
187impl Std140 for Matrix4<f32> {
188    default_write_impl!(16);
189}
190
191impl Std140 for Matrix3<f32> {
192    fn write(&self, dest: &mut dyn ByteStorage) {
193        dest.push_padding(16);
194        for row in (self as &dyn AsRef<[[f32; 3]; 3]>).as_ref() {
195            dest.write_bytes(array_as_u8_slice(row));
196            dest.write_bytes(&[0; size_of::<f32>()]);
197        }
198    }
199}
200
201impl Std140 for Matrix2<f32> {
202    fn write(&self, dest: &mut dyn ByteStorage) {
203        dest.push_padding(16);
204        for row in (self as &dyn AsRef<[[f32; 2]; 2]>).as_ref() {
205            dest.write_bytes(array_as_u8_slice(row));
206            dest.write_bytes(&[0; 2 * size_of::<f32>()]);
207        }
208    }
209}
210
211impl Std140 for Color {
212    fn write(&self, dest: &mut dyn ByteStorage) {
213        dest.push_padding(16);
214        let frgba = self.as_frgba();
215        dest.write_bytes(value_as_u8_slice(&frgba));
216    }
217}
218
219impl Std140 for bool {
220    fn write(&self, dest: &mut dyn ByteStorage) {
221        dest.push_padding(4);
222        let integer = if *self { 1 } else { 0 };
223        dest.write_bytes(value_as_u8_slice(&integer));
224    }
225}
226
227fn write_array(arr: &[impl Std140], dest: &mut dyn ByteStorage) {
228    for item in arr {
229        dest.push_padding(16);
230        item.write(dest);
231        dest.push_padding(16);
232    }
233}
234
235impl<T: Std140, const N: usize> Std140 for [T; N] {
236    fn write(&self, dest: &mut dyn ByteStorage) {
237        write_array(self, dest)
238    }
239}
240
241impl<T: Std140> Std140 for [T] {
242    fn write(&self, dest: &mut dyn ByteStorage) {
243        write_array(self, dest)
244    }
245}
246
247impl<S> UniformBuffer<S>
248where
249    S: ByteStorage,
250{
251    /// Creates a new uniform buffer with an empty storage.
252    pub fn new() -> Self
253    where
254        S: Default,
255    {
256        Self {
257            storage: S::default(),
258        }
259    }
260
261    /// Creates a new uniform buffer with the given storage.
262    pub fn with_storage(storage: S) -> Self {
263        Self { storage }
264    }
265
266    /// Clears the uniform buffer.
267    pub fn clear(&mut self) {
268        self.storage.reset();
269    }
270
271    /// Returns total number of bytes stored in the uniform buffer. Keep in mind, that the number
272    /// in the vast majority of cases won't match the sum of all pushed elements due to alignment
273    /// requirements.
274    pub fn len(&self) -> usize {
275        self.storage.bytes_count()
276    }
277
278    /// Checks if the buffer is empty or not.
279    pub fn is_empty(&self) -> bool {
280        self.len() == 0
281    }
282
283    /// Pushes the given amount of padding bytes to the storage.
284    pub fn push_padding(&mut self, alignment: usize) {
285        self.storage.push_padding(alignment);
286    }
287
288    /// Pushes a value to the storage. This method ensures that the correct alignment for the pushed
289    /// value is preserved.
290    pub fn push<T>(&mut self, value: &T) -> &mut Self
291    where
292        T: Std140 + ?Sized,
293    {
294        value.write(&mut self.storage);
295        self
296    }
297
298    /// The same as [`Self::push`], but allows chained calls in a builder manner.
299    pub fn with<T>(mut self, value: &T) -> Self
300    where
301        T: Std140 + ?Sized,
302    {
303        self.push(value);
304        self
305    }
306
307    fn push_array_element<T: Std140>(&mut self, item: &T) {
308        self.push_padding(16);
309        item.write(&mut self.storage);
310        self.push_padding(16);
311    }
312
313    /// Pushes the given slice into the uniform buffer and pads the rest of the space
314    /// (`max_len - slice_len`) with the default value of the underlying type.
315    pub fn push_slice_with_max_size<T: Std140 + Default>(
316        &mut self,
317        slice: &[T],
318        max_len: usize,
319    ) -> &mut Self {
320        let len = slice.len();
321        if !slice.is_empty() {
322            let end = max_len.min(len);
323            self.push(&slice[0..end]);
324        }
325        let remainder = max_len.saturating_sub(len);
326        let item = T::default();
327        for _ in 0..remainder {
328            self.push_array_element(&item);
329        }
330        self
331    }
332
333    /// Same as [`Self::push_slice_with_max_size`], but allows changed calls with builder-like style.
334    pub fn with_slice_with_max_size<T: Std140 + Default>(
335        mut self,
336        slice: &[T],
337        max_len: usize,
338    ) -> Self {
339        self.push_slice_with_max_size(slice, max_len);
340        self
341    }
342
343    /// Returns a reference to the internal bytes storage of the uniform buffer.
344    pub fn storage(&self) -> &S {
345        &self.storage
346    }
347
348    /// Finishes buffer filling process and returns the backing storage by consuming the buffer. This
349    /// method **must** be called before sending the data GPU, otherwise the buffer may contain misaligned
350    /// data.
351    pub fn finish(mut self) -> S {
352        self.push_padding(16);
353        self.storage
354    }
355
356    /// Calculates position for the next element including the given alignment.
357    pub fn next_write_aligned_position(&self, alignment: usize) -> usize {
358        let position = self.storage.bytes_count();
359        let remainder = (alignment - 1) & position;
360        if remainder > 0 {
361            let padding = alignment - remainder;
362            position + padding
363        } else {
364            position
365        }
366    }
367
368    /// Writes bytes directly to the buffer with the given alignment. Important: this method could
369    /// be dangerous if misused, the alignment argument must be correct and comply with `std140`
370    /// data layout rules.
371    pub fn write_bytes_with_alignment(&mut self, bytes: &[u8], alignment: usize) -> usize {
372        self.push_padding(alignment);
373        let data_location = self.storage.bytes_count();
374        self.storage.write_bytes(bytes);
375        data_location
376    }
377}
378
379#[cfg(test)]
380mod test {
381    use crate::{
382        core::algebra::{Matrix3, Vector3, Vector4},
383        uniform::DynamicUniformBuffer,
384    };
385    use fyrox_core::transmute_slice;
386
387    #[test]
388    fn test_uniform_buffer() {
389        let mut buffer = DynamicUniformBuffer::default();
390        buffer.push(&123.321);
391        assert_eq!(buffer.len(), 4);
392        buffer.push(&Vector3::new(1.0, 2.0, 3.0));
393        assert_eq!(buffer.len(), 28);
394        buffer.push(&Vector4::new(1.0, 2.0, 3.0, 4.0));
395        assert_eq!(buffer.len(), 48);
396        buffer.push(&Matrix3::default());
397        assert_eq!(buffer.len(), 96);
398        buffer.push(&123.0);
399        assert_eq!(buffer.len(), 100);
400        buffer.push(&[1.0, 2.0, 3.0, 4.0]);
401        assert_eq!(buffer.len(), 176);
402        buffer.push(&[1.0, 2.0, 3.0]);
403        assert_eq!(buffer.len(), 224);
404        buffer.push(&[1.0, 2.0]);
405        assert_eq!(buffer.len(), 256);
406        let bytes = buffer.finish();
407        assert_eq!(bytes.len(), 256);
408    }
409
410    #[test]
411    fn test_uniform_buffer_mixed_alignment() {
412        let mut buffer = DynamicUniformBuffer::default();
413        buffer.push(&Vector3::repeat(1.0));
414        assert_eq!(buffer.len(), 12);
415        buffer.push(&1.0);
416        assert_eq!(buffer.len(), 16);
417    }
418
419    #[test]
420    fn test_push_with_max_len() {
421        let mut buffer = DynamicUniformBuffer::default();
422        buffer.push_slice_with_max_size(&[1.0, 2.0, 3.0, 4.0], 6);
423        let floats: &[f32] = transmute_slice(buffer.storage().as_slice());
424        assert_eq!(
425            floats,
426            &[
427                1.0, 0.0, 0.0, 0.0, // 1
428                2.0, 0.0, 0.0, 0.0, // 2
429                3.0, 0.0, 0.0, 0.0, // 3
430                4.0, 0.0, 0.0, 0.0, // 4
431                0.0, 0.0, 0.0, 0.0, // Zero with padding
432                0.0, 0.0, 0.0, 0.0, // Zero with padding
433            ]
434        );
435        buffer.clear();
436        buffer.push_slice_with_max_size(&[1.0, 2.0], 1);
437        let floats: &[f32] = transmute_slice(buffer.storage().as_slice());
438        assert_eq!(floats, &[1.0, 0.0, 0.0, 0.0,]);
439    }
440}