demo_vk/graphics/vulkan/buffers/
uniform_buffer.rs

1use {
2    crate::{
3        graphics::vulkan::{
4            raii, Frame, FramesInFlight, OwnedBlock, VulkanContext,
5        },
6        unwrap_here,
7    },
8    anyhow::{bail, Result},
9    ash::vk,
10    std::marker::PhantomData,
11};
12
13/// A CPU accessible buffer with some convenience functions for uploading
14/// per-frame data.
15#[derive(Debug)]
16pub struct UniformBuffer<DataT: Sized + Copy> {
17    buffer: raii::Buffer,
18    block: OwnedBlock,
19    aligned_unit_size: usize,
20    count: usize,
21    _phantom_data: PhantomData<DataT>,
22}
23
24impl<DataT> UniformBuffer<DataT>
25where
26    DataT: Sized + Copy,
27{
28    /// Allocates a buffer with enough space for count copies of `DataT` aligned
29    /// such that each copy can be bound to a separate descriptor set.
30    pub fn allocate(cxt: &VulkanContext, count: usize) -> Result<Self> {
31        // compute the aligned size for each element in the buffer
32        let properties = {
33            let mut physical_device_properties =
34                vk::PhysicalDeviceProperties2::default();
35            unsafe {
36                cxt.instance.get_physical_device_properties2(
37                    cxt.physical_device,
38                    &mut physical_device_properties,
39                );
40            }
41            physical_device_properties.properties
42        };
43        let aligned_unit_size: u64 = {
44            let count = size_of::<DataT>() as u64
45                / properties.limits.min_uniform_buffer_offset_alignment;
46            (count + 1) * properties.limits.min_uniform_buffer_offset_alignment
47        };
48
49        let buffer_size_in_bytes = aligned_unit_size * count as u64;
50
51        let (block, buffer) = unwrap_here!(
52            "Allocate host visible and coherent memory",
53            OwnedBlock::allocate_buffer(
54                cxt.allocator.clone(),
55                &vk::BufferCreateInfo {
56                    size: buffer_size_in_bytes,
57                    usage: vk::BufferUsageFlags::UNIFORM_BUFFER,
58                    sharing_mode: vk::SharingMode::EXCLUSIVE,
59                    queue_family_index_count: 1,
60                    p_queue_family_indices: &cxt.graphics_queue_family_index,
61                    ..Default::default()
62                },
63                vk::MemoryPropertyFlags::HOST_VISIBLE
64                    | vk::MemoryPropertyFlags::HOST_COHERENT,
65            )
66        );
67
68        Ok(Self {
69            buffer,
70            block,
71            count,
72            aligned_unit_size: aligned_unit_size as usize,
73            _phantom_data: PhantomData,
74        })
75    }
76
77    /// Allocates a new buffer and GPU memory for holding per-frame uniform
78    /// data.
79    pub fn allocate_per_frame(
80        cxt: &VulkanContext,
81        frames_in_flight: &FramesInFlight,
82    ) -> Result<Self> {
83        Self::allocate(cxt, frames_in_flight.frame_count())
84    }
85
86    /// Returns a non-owning copy of the Vulkan buffer handle.
87    pub fn buffer(&self) -> vk::Buffer {
88        self.buffer.raw
89    }
90
91    /// Updates GPU memory with the provided data for the current frame.
92    pub fn update_frame_data(
93        &mut self,
94        frame: &Frame,
95        data: DataT,
96    ) -> Result<()> {
97        // SAFE: because borrowing the Frame means that no pending graphics
98        // commands can still reference the targeted region of the
99        // buffer.
100        unsafe { self.write_indexed(frame.frame_index(), data) }
101    }
102
103    /// Returns the byte-offset into the buffer for the corresponding Frame's
104    /// data.
105    pub fn offset_for_index(&self, frame_index: usize) -> u64 {
106        (frame_index * self.aligned_unit_size) as u64
107    }
108
109    /// Writes data into the GPU memory at the given index.
110    ///
111    /// # Safety
112    ///
113    /// Unsafe because:
114    /// - the caller must synchronize access to the region being written.
115    pub unsafe fn write_indexed(
116        &mut self,
117        index: usize,
118        data: DataT,
119    ) -> Result<()> {
120        if index >= self.count {
121            bail!("Attempt to write to index {}/{}", index, self.count);
122        }
123
124        let offset = self.offset_for_index(index) as isize;
125        std::ptr::copy_nonoverlapping(
126            &data,
127            self.block.mapped_ptr().byte_offset(offset) as *mut DataT,
128            1,
129        );
130
131        Ok(())
132    }
133}