demo_vk/graphics/vulkan/buffers/
uniform_buffer.rs

1use {
2    crate::{
3        graphics::vulkan::{
4            raii, Frame, FramesInFlight, OwnedBlock, VulkanContext,
5        },
6        trace,
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 = unsafe {
33            cxt.instance
34                .get_physical_device_properties(cxt.physical_device)
35        };
36        let aligned_unit_size: u64 = {
37            let count = size_of::<DataT>() as u64
38                / properties.limits.min_uniform_buffer_offset_alignment;
39            (count + 1) * properties.limits.min_uniform_buffer_offset_alignment
40        };
41
42        let buffer_size_in_bytes = aligned_unit_size * count as u64;
43
44        let (block, buffer) = OwnedBlock::allocate_buffer(
45            cxt.allocator.clone(),
46            &vk::BufferCreateInfo {
47                size: buffer_size_in_bytes,
48                usage: vk::BufferUsageFlags::UNIFORM_BUFFER,
49                sharing_mode: vk::SharingMode::EXCLUSIVE,
50                queue_family_index_count: 1,
51                p_queue_family_indices: &cxt.graphics_queue_family_index,
52                ..Default::default()
53            },
54            vk::MemoryPropertyFlags::HOST_VISIBLE
55                | vk::MemoryPropertyFlags::HOST_COHERENT,
56        )?;
57
58        Ok(Self {
59            buffer,
60            block,
61            count,
62            aligned_unit_size: aligned_unit_size as usize,
63            _phantom_data: PhantomData,
64        })
65    }
66
67    /// Allocates a new buffer and GPU memory for holding per-frame uniform
68    /// data.
69    pub fn allocate_per_frame(
70        cxt: &VulkanContext,
71        frames_in_flight: &FramesInFlight,
72    ) -> Result<Self> {
73        Self::allocate(cxt, frames_in_flight.frame_count())
74    }
75
76    /// Returns a non-owning copy of the Vulkan buffer handle.
77    pub fn buffer(&self) -> vk::Buffer {
78        self.buffer.raw
79    }
80
81    /// Updates GPU memory with the provided data for the current frame.
82    pub fn update_frame_data(
83        &mut self,
84        frame: &Frame,
85        data: DataT,
86    ) -> Result<()> {
87        // SAFE: because borrowing the Frame means that no pending graphics
88        // commands can still reference the targeted region of the
89        // buffer.
90        unsafe { self.write_indexed(frame.frame_index(), data) }
91    }
92
93    /// Returns the byte-offset into the buffer for the corresponding Frame's
94    /// data.
95    pub fn offset_for_index(&self, frame_index: usize) -> u64 {
96        (frame_index * self.aligned_unit_size) as u64
97    }
98
99    /// Writes data into the GPU memory at the given index.
100    ///
101    /// # Safety
102    ///
103    /// Unsafe because:
104    /// - the caller must synchronize access to the region being written.
105    pub unsafe fn write_indexed(
106        &mut self,
107        index: usize,
108        data: DataT,
109    ) -> Result<()> {
110        if index >= self.count {
111            bail!(
112                trace!("Attempt to write to index {}/{}", index, self.count)()
113            );
114        }
115
116        let offset = self.offset_for_index(index) as isize;
117        std::ptr::copy_nonoverlapping(
118            &data,
119            self.block.mapped_ptr().byte_offset(offset) as *mut DataT,
120            1,
121        );
122
123        Ok(())
124    }
125}