Skip to main content

pebble/graphics/pipeline/
buffers.rs

1use crate::{
2    ecs::promise::Promise,
3    graphics::{
4        pipeline::{
5            binding::BindGroupLayout, cubemap::GPUCubemap, samplers::Sampler,
6            texture_array::GPUTextureArray, texture_view::TextureView, textures::GPUTexture,
7        },
8        render::{Backend, gpu_context::GpuContext},
9        types::flags::BufferUsages,
10    },
11};
12
13/// An opaque GPU buffer — built via [`BufferBuilder`]. No raw `wgpu` type
14/// appears in its public API. Cheap to `Clone` — it's a handle to the same
15/// underlying GPU buffer (`wgpu::Buffer` is `Arc`-backed), not a copy of
16/// its contents.
17#[derive(Clone)]
18pub struct Buffer {
19    pub(crate) raw: wgpu::Buffer,
20    pub(crate) ctx: GpuContext,
21}
22
23impl Buffer {
24    pub(crate) fn new(raw: wgpu::Buffer, ctx: GpuContext) -> Self {
25        Self { raw, ctx }
26    }
27
28    /// Overwrites the buffer's contents from the start.
29    pub fn write(&self, data: &[u8]) {
30        self.ctx.queue().write_buffer(&self.raw, 0, data);
31    }
32
33    pub fn write_at(&self, offset: u64, data: &[u8]) {
34        self.ctx.queue().write_buffer(&self.raw, offset, data);
35    }
36
37    /// Copies this buffer's contents back to the CPU. Requires
38    /// `BufferUsages::COPY_SRC`. Poll the returned [`Promise`] each tick —
39    /// it resolves once the GPU copy actually finishes.
40    pub fn read(&self) -> Promise<Vec<u8>> {
41        let usage = self.raw.usage();
42        if !usage.contains(wgpu::BufferUsages::COPY_SRC) {
43            panic!(
44                "Buffer::read: buffer is missing BufferUsages::COPY_SRC — it can't be copied out of"
45            );
46        }
47
48        let size = self.raw.size();
49        let staging = self.ctx.device().create_buffer(&wgpu::BufferDescriptor {
50            label: None,
51            size,
52            usage: wgpu::BufferUsages::MAP_READ | wgpu::BufferUsages::COPY_DST,
53            mapped_at_creation: false,
54        });
55
56        let mut encoder = self
57            .ctx
58            .device()
59            .create_command_encoder(&wgpu::CommandEncoderDescriptor::default());
60        encoder.copy_buffer_to_buffer(&self.raw, 0, &staging, 0, size);
61        self.ctx.queue().submit(std::iter::once(encoder.finish()));
62
63        let (fulfiller, promise) = Promise::new();
64        // wgpu::Buffer is Arc-backed/Clone — this keeps the staging buffer
65        // alive for the callback without borrowing `staging` itself
66        let readback = staging.clone();
67        staging.map_async(wgpu::MapMode::Read, .., move |result| {
68            if result.is_ok() {
69                let data = readback
70                    .get_mapped_range(..)
71                    .expect("Failed to get mapped range")
72                    .to_vec();
73                readback.unmap();
74                fulfiller.fulfill(data);
75            }
76            // on error, `fulfiller` drops unfulfilled — Promise::poll
77            // reports Disconnected
78        });
79        promise
80    }
81
82    pub fn size(&self) -> u64 {
83        self.raw.size()
84    }
85
86    pub(crate) fn raw(&self) -> &wgpu::Buffer {
87        &self.raw
88    }
89}
90
91/// A buffer holding many fixed-size elements, each individually writable
92/// and bindable at an aligned offset — for things like per-object uniform
93/// data. Built via [`DynamicBufferBuilder`]. Cheap to `Clone`, same as
94/// [`Buffer`].
95#[derive(Clone)]
96pub struct DynamicBuffer {
97    pub(crate) buffer: Buffer,
98    pub(crate) stride: u64,
99    pub(crate) element_size: u64,
100}
101
102impl DynamicBuffer {
103    pub(crate) fn new(buffer: Buffer, stride: u64, element_size: u64) -> Self {
104        Self {
105            buffer,
106            stride,
107            element_size,
108        }
109    }
110
111    /// Overwrites element `index`'s data.
112    pub fn write_element(&self, index: u64, data: &[u8]) {
113        self.buffer.write_at(index * self.stride, data);
114    }
115
116    pub fn element_size(&self) -> u64 {
117        self.element_size
118    }
119
120    pub fn stride(&self) -> u64 {
121        self.stride
122    }
123}
124
125enum BufferContents<'a> {
126    Empty(u64),
127    Data(&'a [u8]),
128}
129
130impl<'a> BufferContents<'a> {
131    fn size(&self) -> u64 {
132        match self {
133            BufferContents::Empty(size) => *size,
134            BufferContents::Data(data) => data.len() as u64,
135        }
136    }
137}
138
139/// Builds a [`Buffer`] — `empty(size)` or `with_data(bytes)`, then
140/// `.with_usage(...)`/`.build(backend)`.
141pub struct BufferBuilder<'a> {
142    label: Option<&'a str>,
143    usage: BufferUsages,
144    contents: BufferContents<'a>,
145}
146
147impl<'a> BufferBuilder<'a> {
148    pub fn empty(size: u64) -> Self {
149        Self {
150            label: None,
151            usage: BufferUsages::empty(),
152            contents: BufferContents::Empty(size),
153        }
154    }
155
156    pub fn with_data(data: &'a [u8]) -> Self {
157        Self {
158            label: None,
159            usage: BufferUsages::empty(),
160            contents: BufferContents::Data(data),
161        }
162    }
163
164    pub fn with_label(mut self, label: impl Into<Option<&'a str>>) -> Self {
165        self.label = label.into();
166        self
167    }
168
169    pub fn with_usage(mut self, usage: BufferUsages) -> Self {
170        self.usage = usage;
171        self
172    }
173
174    pub fn with_uniform(self) -> Self {
175        self.with_usage(BufferUsages::UNIFORM | BufferUsages::COPY_DST)
176    }
177
178    pub fn with_storage(self) -> Self {
179        self.with_usage(BufferUsages::STORAGE | BufferUsages::COPY_DST)
180    }
181
182    pub fn build(self, backend: &Backend) -> Buffer {
183        let device = &backend.device;
184        check_buffer_size(device, self.label, self.usage, self.contents.size());
185        let raw = match self.contents {
186            BufferContents::Data(data) => {
187                use wgpu::util::DeviceExt;
188                device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
189                    label: self.label,
190                    contents: data,
191                    usage: self.usage.into(),
192                })
193            }
194            BufferContents::Empty(size) => device.create_buffer(&wgpu::BufferDescriptor {
195                label: self.label,
196                size,
197                usage: self.usage.into(),
198                mapped_at_creation: false,
199            }),
200        };
201        Buffer::new(raw, GpuContext::from_backend(backend))
202    }
203}
204
205fn check_buffer_size(device: &wgpu::Device, label: Option<&str>, usage: BufferUsages, size: u64) {
206    let limits = device.limits();
207    let labeled = || label.map(|l| format!(" '{l}'")).unwrap_or_default();
208    if size > limits.max_buffer_size {
209        panic!(
210            "buffer{}: {size} bytes exceeds this device's max_buffer_size ({})",
211            labeled(),
212            limits.max_buffer_size
213        );
214    }
215    if usage.contains(BufferUsages::UNIFORM) && size > limits.max_uniform_buffer_binding_size {
216        panic!(
217            "buffer{}: {size} bytes exceeds this device's max_uniform_buffer_binding_size ({})",
218            labeled(),
219            limits.max_uniform_buffer_binding_size
220        );
221    }
222    if usage.contains(BufferUsages::STORAGE) && size > limits.max_storage_buffer_binding_size {
223        panic!(
224            "buffer{}: {size} bytes exceeds this device's max_storage_buffer_binding_size ({})",
225            labeled(),
226            limits.max_storage_buffer_binding_size
227        );
228    }
229}
230
231enum DynamicKind {
232    Uniform,
233    Storage,
234}
235
236/// Builds a [`DynamicBuffer`] — `uniform(element_size, count)`/`storage(...)`,
237/// then `.build(backend)`. The actual per-element stride is rounded up to
238/// the device's required offset alignment automatically.
239pub struct DynamicBufferBuilder<'a> {
240    label: Option<&'a str>,
241    kind: DynamicKind,
242    element_size: u64,
243    count: u64,
244}
245
246impl<'a> DynamicBufferBuilder<'a> {
247    pub fn uniform(element_size: u64, count: u64) -> Self {
248        Self {
249            label: None,
250            kind: DynamicKind::Uniform,
251            element_size,
252            count,
253        }
254    }
255
256    pub fn storage(element_size: u64, count: u64) -> Self {
257        Self {
258            label: None,
259            kind: DynamicKind::Storage,
260            element_size,
261            count,
262        }
263    }
264
265    pub fn with_label(mut self, label: impl Into<Option<&'a str>>) -> Self {
266        self.label = label.into();
267        self
268    }
269
270    pub fn build(self, backend: &Backend) -> DynamicBuffer {
271        let (usage, stride) = match self.kind {
272            DynamicKind::Uniform => (
273                BufferUsages::UNIFORM | BufferUsages::COPY_DST,
274                dynamic_uniform_offset_stride(backend, self.element_size),
275            ),
276            DynamicKind::Storage => (
277                BufferUsages::STORAGE | BufferUsages::COPY_DST,
278                dynamic_storage_offset_stride(backend, self.element_size),
279            ),
280        };
281        let buffer = BufferBuilder::empty(stride * self.count)
282            .with_label(self.label)
283            .with_usage(usage)
284            .build(backend);
285        DynamicBuffer::new(buffer, stride, self.element_size)
286    }
287}
288
289pub fn dynamic_uniform_offset_stride(backend: &Backend, element_size: u64) -> u64 {
290    align_to(
291        element_size,
292        backend.device.limits().min_uniform_buffer_offset_alignment as u64,
293    )
294}
295
296pub fn dynamic_storage_offset_stride(backend: &Backend, element_size: u64) -> u64 {
297    align_to(
298        element_size,
299        backend.device.limits().min_storage_buffer_offset_alignment as u64,
300    )
301}
302
303fn align_to(size: u64, alignment: u64) -> u64 {
304    size.div_ceil(alignment) * alignment
305}
306
307fn dynamic_buffer_binding(buffer: &wgpu::Buffer, element_size: u64) -> wgpu::BindingResource<'_> {
308    wgpu::BindingResource::Buffer(wgpu::BufferBinding {
309        buffer,
310        offset: 0,
311        size: wgpu::BufferSize::new(element_size),
312    })
313}
314
315/// An opaque bind group — built via [`BindGroupBuilder`].
316pub struct BindGroup(wgpu::BindGroup);
317
318impl BindGroup {
319    pub(crate) fn raw(&self) -> &wgpu::BindGroup {
320        &self.0
321    }
322}
323
324/// Builds a [`BindGroup`] against a [`BindGroupLayout`] — `.with_buffer(...)`/
325/// `.with_texture_2d(...)`/`.with_sampler(...)`/etc. in binding order (or
326/// the `_at(binding, ...)` variant to place one explicitly), then
327/// `.build(backend)`.
328pub struct BindGroupBuilder<'a> {
329    label: Option<&'a str>,
330    layout: &'a wgpu::BindGroupLayout,
331    entries: Vec<wgpu::BindGroupEntry<'a>>,
332    next_binding: u32,
333}
334
335impl<'a> BindGroupBuilder<'a> {
336    pub fn new(layout: &'a BindGroupLayout) -> Self {
337        Self {
338            label: None,
339            layout: layout.raw(),
340            entries: Vec::new(),
341            next_binding: 0,
342        }
343    }
344
345    pub fn with_label(mut self, label: impl Into<Option<&'a str>>) -> Self {
346        self.label = label.into();
347        self
348    }
349
350    pub fn with_buffer(self, buffer: &'a Buffer) -> Self {
351        let binding = self.next_binding;
352        self.with_buffer_at(binding, buffer)
353    }
354
355    pub fn with_buffer_at(mut self, binding: u32, buffer: &'a Buffer) -> Self {
356        self.entries.push(wgpu::BindGroupEntry {
357            binding,
358            resource: buffer.raw().as_entire_binding(),
359        });
360        self.next_binding = self.next_binding.max(binding + 1);
361        self
362    }
363
364    pub fn with_dynamic_buffer(self, buffer: &'a DynamicBuffer) -> Self {
365        let binding = self.next_binding;
366        self.with_dynamic_buffer_at(binding, buffer)
367    }
368
369    pub fn with_dynamic_buffer_at(mut self, binding: u32, buffer: &'a DynamicBuffer) -> Self {
370        let resource = dynamic_buffer_binding(buffer.buffer.raw(), buffer.element_size);
371        self.entries
372            .push(wgpu::BindGroupEntry { binding, resource });
373        self.next_binding = self.next_binding.max(binding + 1);
374        self
375    }
376
377    pub fn with_texture_2d(self, texture: &'a GPUTexture) -> Self {
378        let binding = self.next_binding;
379        self.with_texture_2d_at(binding, texture)
380    }
381
382    pub fn with_texture_2d_at(self, binding: u32, texture: &'a GPUTexture) -> Self {
383        self.texture_view_raw_at(binding, texture.view())
384    }
385
386    pub fn with_texture_array(self, texture: &'a GPUTextureArray) -> Self {
387        let binding = self.next_binding;
388        self.with_texture_array_at(binding, texture)
389    }
390
391    pub fn with_texture_array_at(self, binding: u32, texture: &'a GPUTextureArray) -> Self {
392        self.texture_view_raw_at(binding, texture.view())
393    }
394
395    pub fn with_texture_cubemap(self, texture: &'a GPUCubemap) -> Self {
396        let binding = self.next_binding;
397        self.with_texture_cubemap_at(binding, texture)
398    }
399
400    pub fn with_texture_cubemap_at(self, binding: u32, texture: &'a GPUCubemap) -> Self {
401        self.texture_view_raw_at(binding, texture.view())
402    }
403
404    pub fn with_texture_view(self, view: &'a TextureView) -> Self {
405        let binding = self.next_binding;
406        self.with_texture_view_at(binding, view)
407    }
408
409    pub fn with_texture_view_at(self, binding: u32, view: &'a TextureView) -> Self {
410        self.texture_view_raw_at(binding, view.raw())
411    }
412
413    pub(crate) fn texture_view_raw_at(mut self, binding: u32, view: &'a wgpu::TextureView) -> Self {
414        self.entries.push(wgpu::BindGroupEntry {
415            binding,
416            resource: wgpu::BindingResource::TextureView(view),
417        });
418        self.next_binding = self.next_binding.max(binding + 1);
419        self
420    }
421
422    pub fn with_sampler(self, sampler: &'a Sampler) -> Self {
423        let binding = self.next_binding;
424        self.with_sampler_at(binding, sampler)
425    }
426
427    pub fn with_sampler_at(mut self, binding: u32, sampler: &'a Sampler) -> Self {
428        self.entries.push(wgpu::BindGroupEntry {
429            binding,
430            resource: wgpu::BindingResource::Sampler(sampler.raw()),
431        });
432        self.next_binding = self.next_binding.max(binding + 1);
433        self
434    }
435
436    pub fn build(self, backend: &Backend) -> BindGroup {
437        BindGroup(
438            backend
439                .device
440                .create_bind_group(&wgpu::BindGroupDescriptor {
441                    label: self.label,
442                    layout: self.layout,
443                    entries: &self.entries,
444                }),
445        )
446    }
447}