|
1 |
| -# Shader Buffer |
| 1 | +# Descriptor Buffer |
2 | 2 |
|
3 |
| -Uniform and Storage buffers need to be N-buffered unless they are "GPU const", ie contents do not change after creation. Encapsulate a `vma::Buffer` per virtual frame in a `ShaderBuffer`: |
| 3 | +Uniform and Storage buffers need to be N-buffered unless they are "GPU const", ie contents do not change after creation. Encapsulate a `vma::Buffer` per virtual frame in a `DescriptorBuffer`: |
4 | 4 |
|
5 | 5 | ```cpp
|
6 |
| -class ShaderBuffer { |
7 |
| - public: |
8 |
| - explicit ShaderBuffer(VmaAllocator allocator, std::uint32_t queue_family, |
9 |
| - vk::BufferUsageFlags usage); |
| 6 | +class DescriptorBuffer { |
| 7 | + public: |
| 8 | + explicit DescriptorBuffer(VmaAllocator allocator, |
| 9 | + std::uint32_t queue_family, |
| 10 | + vk::BufferUsageFlags usage); |
10 | 11 |
|
11 |
| - void write_at(std::size_t frame_index, std::span<std::byte const> bytes); |
| 12 | + void write_at(std::size_t frame_index, std::span<std::byte const> bytes); |
12 | 13 |
|
13 |
| - [[nodiscard]] auto descriptor_info_at(std::size_t frame_index) const |
14 |
| - -> vk::DescriptorBufferInfo; |
| 14 | + [[nodiscard]] auto descriptor_info_at(std::size_t frame_index) const |
| 15 | + -> vk::DescriptorBufferInfo; |
15 | 16 |
|
16 |
| - private: |
17 |
| - struct Buffer { |
18 |
| - vma::Buffer buffer{}; |
19 |
| - vk::DeviceSize size{}; |
20 |
| - }; |
| 17 | + private: |
| 18 | + struct Buffer { |
| 19 | + vma::Buffer buffer{}; |
| 20 | + vk::DeviceSize size{}; |
| 21 | + }; |
21 | 22 |
|
22 |
| - void write_to(Buffer& out, std::span<std::byte const> bytes) const; |
| 23 | + void write_to(Buffer& out, std::span<std::byte const> bytes) const; |
23 | 24 |
|
24 |
| - VmaAllocator m_allocator{}; |
25 |
| - std::uint32_t m_queue_family{}; |
26 |
| - vk::BufferUsageFlags m_usage{}; |
27 |
| - Buffered<Buffer> m_buffers{}; |
| 25 | + VmaAllocator m_allocator{}; |
| 26 | + std::uint32_t m_queue_family{}; |
| 27 | + vk::BufferUsageFlags m_usage{}; |
| 28 | + Buffered<Buffer> m_buffers{}; |
28 | 29 | };
|
29 | 30 | ```
|
30 | 31 |
|
31 | 32 | The implementation is fairly straightforward, it reuses existing buffers if they are large enough, else recreates them before copying data. It also ensures buffers are always valid to be bound to descriptors.
|
32 | 33 |
|
33 | 34 | ```cpp
|
34 |
| -ShaderBuffer::ShaderBuffer(VmaAllocator allocator, |
35 |
| - std::uint32_t const queue_family, |
36 |
| - vk::BufferUsageFlags const usage) |
37 |
| - : m_allocator(allocator), m_queue_family(queue_family), m_usage(usage) { |
38 |
| - // ensure buffers are created and can be bound after returning. |
39 |
| - for (auto& buffer : m_buffers) { write_to(buffer, {}); } |
| 35 | +DescriptorBuffer::DescriptorBuffer(VmaAllocator allocator, |
| 36 | + std::uint32_t const queue_family, |
| 37 | + vk::BufferUsageFlags const usage) |
| 38 | + : m_allocator(allocator), m_queue_family(queue_family), m_usage(usage) { |
| 39 | + // ensure buffers are created and can be bound after returning. |
| 40 | + for (auto& buffer : m_buffers) { write_to(buffer, {}); } |
40 | 41 | }
|
41 | 42 |
|
42 |
| -void ShaderBuffer::write_at(std::size_t const frame_index, |
43 |
| - std::span<std::byte const> bytes) { |
44 |
| - write_to(m_buffers.at(frame_index), bytes); |
| 43 | +void DescriptorBuffer::write_at(std::size_t const frame_index, |
| 44 | + std::span<std::byte const> bytes) { |
| 45 | + write_to(m_buffers.at(frame_index), bytes); |
45 | 46 | }
|
46 | 47 |
|
47 |
| -auto ShaderBuffer::descriptor_info_at(std::size_t const frame_index) const |
48 |
| - -> vk::DescriptorBufferInfo { |
49 |
| - auto const& buffer = m_buffers.at(frame_index); |
50 |
| - auto ret = vk::DescriptorBufferInfo{}; |
51 |
| - ret.setBuffer(buffer.buffer.get().buffer).setRange(buffer.size); |
52 |
| - return ret; |
| 48 | +auto DescriptorBuffer::descriptor_info_at(std::size_t const frame_index) const |
| 49 | + -> vk::DescriptorBufferInfo { |
| 50 | + auto const& buffer = m_buffers.at(frame_index); |
| 51 | + auto ret = vk::DescriptorBufferInfo{}; |
| 52 | + ret.setBuffer(buffer.buffer.get().buffer).setRange(buffer.size); |
| 53 | + return ret; |
53 | 54 | }
|
54 | 55 |
|
55 |
| -void ShaderBuffer::write_to(Buffer& out, |
56 |
| - std::span<std::byte const> bytes) const { |
57 |
| - static constexpr auto blank_byte_v = std::array{std::byte{}}; |
58 |
| - // fallback to an empty byte if bytes is empty. |
59 |
| - if (bytes.empty()) { bytes = blank_byte_v; } |
60 |
| - out.size = bytes.size(); |
61 |
| - if (out.buffer.get().size < bytes.size()) { |
62 |
| - // size is too small (or buffer doesn't exist yet), recreate buffer. |
63 |
| - auto const buffer_ci = vma::BufferCreateInfo{ |
64 |
| - .allocator = m_allocator, |
65 |
| - .usage = m_usage, |
66 |
| - .queue_family = m_queue_family, |
67 |
| - }; |
68 |
| - out.buffer = vma::create_buffer(buffer_ci, vma::BufferMemoryType::Host, |
69 |
| - out.size); |
70 |
| - } |
71 |
| - std::memcpy(out.buffer.get().mapped, bytes.data(), bytes.size()); |
| 56 | +void DescriptorBuffer::write_to(Buffer& out, |
| 57 | + std::span<std::byte const> bytes) const { |
| 58 | + static constexpr auto blank_byte_v = std::array{std::byte{}}; |
| 59 | + // fallback to an empty byte if bytes is empty. |
| 60 | + if (bytes.empty()) { bytes = blank_byte_v; } |
| 61 | + out.size = bytes.size(); |
| 62 | + if (out.buffer.get().size < bytes.size()) { |
| 63 | + // size is too small (or buffer doesn't exist yet), recreate buffer. |
| 64 | + auto const buffer_ci = vma::BufferCreateInfo{ |
| 65 | + .allocator = m_allocator, |
| 66 | + .usage = m_usage, |
| 67 | + .queue_family = m_queue_family, |
| 68 | + }; |
| 69 | + out.buffer = vma::create_buffer(buffer_ci, vma::BufferMemoryType::Host, |
| 70 | + out.size); |
| 71 | + } |
| 72 | + std::memcpy(out.buffer.get().mapped, bytes.data(), bytes.size()); |
72 | 73 | }
|
73 | 74 | ```
|
74 | 75 |
|
75 |
| -Store a `ShaderBuffer` in `App` and rename `create_vertex_buffer()` to `create_shader_resources()`: |
| 76 | +Store a `DescriptorBuffer` in `App` and rename `create_vertex_buffer()` to `create_shader_resources()`: |
76 | 77 |
|
77 | 78 | ```cpp
|
78 |
| -std::optional<ShaderBuffer> m_view_ubo{}; |
| 79 | +std::optional<DescriptorBuffer> m_view_ubo{}; |
79 | 80 |
|
80 | 81 | // ...
|
81 | 82 | m_vbo = vma::create_device_buffer(buffer_ci, create_command_block(),
|
@@ -170,4 +171,4 @@ static constexpr auto vertices_v = std::array{
|
170 | 171 |
|
171 | 172 | 
|
172 | 173 |
|
173 |
| -When such shader buffers are created and (more importantly) destroyed dynamically, they would need to store a `ScopedWaiter` to ensure all rendering with descriptor sets bound to them completes before destruction. Alternatively, the app can maintain a pool of scratch buffers (similar to small/dynamic vertex buffers) per virtual frame which get destroyed in a batch instead of individually. |
| 174 | +When such descriptor buffers are created and destroyed dynamically, they would need to store a `ScopedWaiter` to ensure all rendering with descriptor sets bound to them completes before destruction. Alternatively, the app can maintain a pool of scratch buffers (similar to small/dynamic vertex buffers) per virtual frame which get destroyed in a batch instead of individually. |
0 commit comments