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