Skip to main content

pebble/wgpu/
buffers.rs

1pub enum BufferSource<'a> {
2    Data(&'a [u8]),
3    Buffer(wgpu::Buffer),
4}
5
6impl<'a> From<&'a [u8]> for BufferSource<'a> {
7    fn from(data: &'a [u8]) -> Self {
8        BufferSource::Data(data)
9    }
10}
11
12impl<'a> From<wgpu::Buffer> for BufferSource<'a> {
13    fn from(buffer: wgpu::Buffer) -> Self {
14        BufferSource::Buffer(buffer)
15    }
16}
17
18pub enum BindingResource<'a> {
19    UniformBuffer(BufferSource<'a>),
20    StorageBuffer(BufferSource<'a>),
21    /// A uniform buffer bound with a dynamic offset. The bind group entry is scoped to a
22    /// single `element_size`-sized element (see [`dynamic_buffer_binding`]) rather than the
23    /// whole buffer, so it works with the dynamic offset passed to `set_bind_group`.
24    DynamicUniformBuffer { buffer: wgpu::Buffer, element_size: u64 },
25    /// Same as `DynamicUniformBuffer` but for a storage buffer.
26    DynamicStorageBuffer { buffer: wgpu::Buffer, element_size: u64 },
27    TextureView(&'a wgpu::TextureView),
28    Sampler(&'a wgpu::Sampler),
29}
30
31pub fn build_buffer(device: &wgpu::Device, contents: &[u8], usage: wgpu::BufferUsages) -> wgpu::Buffer {
32    use wgpu::util::DeviceExt;
33    device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
34        label: None,
35        contents,
36        usage,
37    })
38}
39
40pub fn build_buffer_sized(device: &wgpu::Device, size: u64, usage: wgpu::BufferUsages) -> wgpu::Buffer {
41    device.create_buffer(&wgpu::BufferDescriptor {
42        label: None,
43        size,
44        usage,
45        mapped_at_creation: false,
46    })
47}
48
49pub fn resolve_buffer(device: &wgpu::Device, source: BufferSource<'_>, usage: wgpu::BufferUsages) -> wgpu::Buffer {
50    match source {
51        BufferSource::Data(data) => build_buffer(device, data, usage),
52        BufferSource::Buffer(buffer) => buffer,
53    }
54}
55
56pub fn resolve_uniform_buffer(device: &wgpu::Device, source: BufferSource<'_>) -> wgpu::Buffer {
57    resolve_buffer(device, source, wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST)
58}
59
60pub fn resolve_storage_buffer(device: &wgpu::Device, source: BufferSource<'_>) -> wgpu::Buffer {
61    resolve_buffer(device, source, wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_DST)
62}
63
64pub fn build_uniform_bind_group<'a>(
65    device: &wgpu::Device,
66    layout: &wgpu::BindGroupLayout,
67    source: impl Into<BufferSource<'a>>,
68) -> (wgpu::Buffer, wgpu::BindGroup) {
69    let (mut buffers, bind_group) = build_bind_group(device, layout, vec![BindingResource::UniformBuffer(source.into())]);
70    (buffers.remove(0), bind_group)
71}
72
73pub fn build_storage_bind_group<'a>(
74    device: &wgpu::Device,
75    layout: &wgpu::BindGroupLayout,
76    source: impl Into<BufferSource<'a>>,
77) -> (wgpu::Buffer, wgpu::BindGroup) {
78    let (mut buffers, bind_group) = build_bind_group(device, layout, vec![BindingResource::StorageBuffer(source.into())]);
79    (buffers.remove(0), bind_group)
80}
81
82/// Allocates a dynamically-offset uniform buffer sized for `count` elements and builds a
83/// bind group for it in one step. Returns the buffer, the per-element stride to use as the
84/// dynamic offset in `set_bind_group`, and the bind group. Use with a layout built from
85/// [`crate::wgpu::material::MaterialBindingKind::dynamic_uniform_buffer`] (or the
86/// `ComputeBindingKind` equivalent).
87pub fn build_dynamic_uniform_bind_group(
88    device: &wgpu::Device,
89    layout: &wgpu::BindGroupLayout,
90    element_size: u64,
91    count: u64,
92) -> (wgpu::Buffer, u64, wgpu::BindGroup) {
93    let (buffer, stride) = build_dynamic_uniform_buffer(device, element_size, count);
94    let (mut buffers, bind_group) =
95        build_bind_group(device, layout, vec![BindingResource::DynamicUniformBuffer { buffer, element_size }]);
96    (buffers.remove(0), stride, bind_group)
97}
98
99/// Same as [`build_dynamic_uniform_bind_group`] but for a storage buffer.
100pub fn build_dynamic_storage_bind_group(
101    device: &wgpu::Device,
102    layout: &wgpu::BindGroupLayout,
103    element_size: u64,
104    count: u64,
105) -> (wgpu::Buffer, u64, wgpu::BindGroup) {
106    let (buffer, stride) = build_dynamic_storage_buffer(device, element_size, count);
107    let (mut buffers, bind_group) =
108        build_bind_group(device, layout, vec![BindingResource::DynamicStorageBuffer { buffer, element_size }]);
109    (buffers.remove(0), stride, bind_group)
110}
111
112pub fn update_uniform_buffer(queue: &wgpu::Queue, buffer: &wgpu::Buffer, data: &[u8]) {
113    queue.write_buffer(buffer, 0, data);
114}
115
116/// Writes `data` into `buffer` at a byte offset, for updating one element of a
117/// dynamically-offset buffer without touching the others. `offset` should be a
118/// multiple of the stride returned by [`dynamic_offset_stride`].
119pub fn update_buffer_at(queue: &wgpu::Queue, buffer: &wgpu::Buffer, offset: u64, data: &[u8]) {
120    queue.write_buffer(buffer, offset, data);
121}
122
123/// Rounds `element_size` up to the device's required alignment for dynamic offsets on
124/// uniform buffers, giving the stride to use when packing multiple elements into one
125/// buffer for use with [`MaterialBindingKind::dynamic_uniform_buffer`](super::material::MaterialBindingKind::dynamic_uniform_buffer).
126pub fn dynamic_uniform_offset_stride(device: &wgpu::Device, element_size: u64) -> u64 {
127    align_to(element_size, device.limits().min_uniform_buffer_offset_alignment as u64)
128}
129
130/// Same as [`dynamic_uniform_offset_stride`] but for storage buffers.
131pub fn dynamic_storage_offset_stride(device: &wgpu::Device, element_size: u64) -> u64 {
132    align_to(element_size, device.limits().min_storage_buffer_offset_alignment as u64)
133}
134
135fn align_to(size: u64, alignment: u64) -> u64 {
136    size.div_ceil(alignment) * alignment
137}
138
139/// Builds the bind group entry resource for a dynamically-offset binding. Unlike
140/// `buffer.as_entire_binding()`, this scopes the entry to a single `element_size`-sized
141/// element starting at offset 0 in the buffer — required because the dynamic offset passed
142/// to `set_bind_group` at draw/dispatch time is added on top of this base range, and wgpu
143/// validates `offset + size <= buffer size`. Binding the whole buffer here would make any
144/// nonzero dynamic offset fail validation.
145pub fn dynamic_buffer_binding(buffer: &wgpu::Buffer, element_size: u64) -> wgpu::BindingResource<'_> {
146    wgpu::BindingResource::Buffer(wgpu::BufferBinding {
147        buffer,
148        offset: 0,
149        size: wgpu::BufferSize::new(element_size),
150    })
151}
152
153/// Builds an empty buffer sized to hold `count` elements of a dynamically-offset uniform
154/// buffer, and returns the buffer along with the per-element stride to use as dynamic
155/// offsets in `RenderPass::set_bind_group`.
156pub fn build_dynamic_uniform_buffer(device: &wgpu::Device, element_size: u64, count: u64) -> (wgpu::Buffer, u64) {
157    let stride = dynamic_uniform_offset_stride(device, element_size);
158    let buffer = build_buffer_sized(device, stride * count, wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST);
159    (buffer, stride)
160}
161
162/// Builds an empty buffer sized to hold `count` elements of a dynamically-offset storage
163/// buffer, and returns the buffer along with the per-element stride.
164pub fn build_dynamic_storage_buffer(device: &wgpu::Device, element_size: u64, count: u64) -> (wgpu::Buffer, u64) {
165    let stride = dynamic_storage_offset_stride(device, element_size);
166    let buffer = build_buffer_sized(device, stride * count, wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_DST);
167    (buffer, stride)
168}
169
170/// Builds a bind group from multiple resources. Buffers created from `Data` are returned
171/// in order (texture views and samplers are not returned). Pre-built buffers passed via
172/// `Buffer` are consumed and also returned.
173pub fn build_bind_group<'a>(
174    device: &wgpu::Device,
175    layout: &wgpu::BindGroupLayout,
176    resources: Vec<BindingResource<'a>>,
177) -> (Vec<wgpu::Buffer>, wgpu::BindGroup) {
178    enum Resolved<'a> {
179        Buffer(wgpu::Buffer),
180        DynamicBuffer(wgpu::Buffer, u64),
181        TextureView(&'a wgpu::TextureView),
182        Sampler(&'a wgpu::Sampler),
183    }
184
185    let resolved: Vec<Resolved> = resources
186        .into_iter()
187        .map(|r| match r {
188            BindingResource::UniformBuffer(src) => Resolved::Buffer(resolve_uniform_buffer(device, src)),
189            BindingResource::StorageBuffer(src) => Resolved::Buffer(resolve_storage_buffer(device, src)),
190            BindingResource::DynamicUniformBuffer { buffer, element_size } => Resolved::DynamicBuffer(buffer, element_size),
191            BindingResource::DynamicStorageBuffer { buffer, element_size } => Resolved::DynamicBuffer(buffer, element_size),
192            BindingResource::TextureView(view) => Resolved::TextureView(view),
193            BindingResource::Sampler(sampler) => Resolved::Sampler(sampler),
194        })
195        .collect();
196
197    let bind_group = {
198        let entries: Vec<wgpu::BindGroupEntry> = resolved
199            .iter()
200            .enumerate()
201            .map(|(i, r)| wgpu::BindGroupEntry {
202                binding: i as u32,
203                resource: match r {
204                    Resolved::Buffer(buf) => buf.as_entire_binding(),
205                    Resolved::DynamicBuffer(buf, element_size) => dynamic_buffer_binding(buf, *element_size),
206                    Resolved::TextureView(view) => wgpu::BindingResource::TextureView(view),
207                    Resolved::Sampler(sampler) => wgpu::BindingResource::Sampler(sampler),
208                },
209            })
210            .collect();
211
212        device.create_bind_group(&wgpu::BindGroupDescriptor {
213            label: None,
214            layout,
215            entries: &entries,
216        })
217    };
218
219    let buffers = resolved
220        .into_iter()
221        .filter_map(|r| match r {
222            Resolved::Buffer(buf) => Some(buf),
223            Resolved::DynamicBuffer(buf, _) => Some(buf),
224            _ => None,
225        })
226        .collect();
227
228    (buffers, bind_group)
229}