use crate::threading::SpawnableFuture;
use crate::wgpu::gpu_context::GpuContext;
pub struct Buffer {
pub(crate) raw: wgpu::Buffer,
pub(crate) ctx: GpuContext,
}
impl Buffer {
pub(crate) fn new(raw: wgpu::Buffer, ctx: GpuContext) -> Self {
Self { raw, ctx }
}
pub fn write(&self, data: &[u8]) {
self.ctx.queue().write_buffer(&self.raw, 0, data);
}
pub fn write_at(&self, offset: u64, data: &[u8]) {
self.ctx.queue().write_buffer(&self.raw, offset, data);
}
pub fn size(&self) -> u64 {
self.raw.size()
}
pub fn read(&self) -> impl SpawnableFuture<Vec<u8>> {
readback(self.ctx.device(), self.ctx.queue(), &self.raw)
}
pub fn read_as<T: bytemuck::Pod + Send + 'static>(&self) -> impl SpawnableFuture<Vec<T>> {
let bytes = self.read();
async move {
let bytes = bytes.await;
bytemuck::cast_slice(&bytes).to_vec()
}
}
pub(crate) fn raw(&self) -> &wgpu::Buffer {
&self.raw
}
}
pub(crate) fn readback(
device: &wgpu::Device,
queue: &wgpu::Queue,
src: &wgpu::Buffer,
) -> impl SpawnableFuture<Vec<u8>> {
let size = src.size();
let staging = crate::wgpu::buffers::BufferBuilder::new()
.usage(crate::wgpu::flags::BufferUsages::COPY_DST | crate::wgpu::flags::BufferUsages::MAP_READ)
.size(size)
.build_raw(device);
let mut encoder = device.create_command_encoder(&wgpu::CommandEncoderDescriptor::default());
encoder.copy_buffer_to_buffer(src, 0, &staging, 0, size);
let idx = queue.submit(std::iter::once(encoder.finish()));
#[cfg(not(target_arch = "wasm32"))]
let device = device.clone();
async move {
#[cfg(not(target_arch = "wasm32"))]
{
let (tx, rx) = std::sync::mpsc::channel();
staging.slice(..).map_async(wgpu::MapMode::Read, move |r| {
let _ = tx.send(r);
});
let _ = device.poll(wgpu::PollType::Wait {
submission_index: Some(idx),
timeout: None,
});
rx.recv().unwrap().unwrap();
let data = staging.slice(..).get_mapped_range().to_vec();
staging.unmap();
data
}
#[cfg(target_arch = "wasm32")]
{
let _ = idx;
let mapped: std::sync::Arc<std::sync::Mutex<Option<Result<(), wgpu::BufferAsyncError>>>> =
std::sync::Arc::new(std::sync::Mutex::new(None));
let waker: std::sync::Arc<std::sync::Mutex<Option<std::task::Waker>>> =
std::sync::Arc::new(std::sync::Mutex::new(None));
let mapped_cb = mapped.clone();
let waker_cb = waker.clone();
staging.slice(..).map_async(wgpu::MapMode::Read, move |r| {
*mapped_cb.lock().unwrap() = Some(r);
if let Some(w) = waker_cb.lock().unwrap().take() {
w.wake();
}
});
std::future::poll_fn(move |cx| {
if let Some(result) = mapped.lock().unwrap().take() {
result.unwrap();
let data = staging.slice(..).get_mapped_range().to_vec();
staging.unmap();
std::task::Poll::Ready(data)
} else {
*waker.lock().unwrap() = Some(cx.waker().clone());
std::task::Poll::Pending
}
})
.await
}
}
}
pub struct DynamicBuffer {
pub(crate) buffer: Buffer,
pub(crate) stride: u64,
pub(crate) element_size: u64,
}
impl DynamicBuffer {
pub(crate) fn new(buffer: Buffer, stride: u64, element_size: u64) -> Self {
Self { buffer, stride, element_size }
}
pub fn write_element(&self, index: u64, data: &[u8]) {
self.buffer.write_at(index * self.stride, data);
}
pub fn element_size(&self) -> u64 {
self.element_size
}
pub fn stride(&self) -> u64 {
self.stride
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::wgpu::buffers::BufferBuilder;
use crate::wgpu::flags::BufferUsages;
use crate::wgpu::test_util::with_device;
fn ctx(device: &wgpu::Device, queue: &wgpu::Queue) -> GpuContext {
GpuContext::new(device.clone(), queue.clone())
}
#[test]
fn write_and_write_at_do_not_panic() {
with_device!(device, queue, {
let buffer = Buffer::new(
BufferBuilder::new().usage(BufferUsages::UNIFORM | BufferUsages::COPY_DST).size(16).build_raw(&device),
ctx(&device, &queue),
);
buffer.write(&[1u8, 2, 3, 4]);
buffer.write_at(8, &[5u8, 6, 7, 8]);
assert_eq!(buffer.size(), 16);
});
}
#[test]
fn dynamic_buffer_write_element_does_not_panic_and_reports_its_own_sizing() {
with_device!(device, queue, {
let element_size = 16u64;
let count = 4u64;
let (usage, stride) = (
BufferUsages::UNIFORM | BufferUsages::COPY_DST,
crate::wgpu::buffers::dynamic_uniform_offset_stride_raw(&device, element_size),
);
let raw = BufferBuilder::new().usage(usage).size(stride * count).build_raw(&device);
let dynamic = DynamicBuffer::new(Buffer::new(raw, ctx(&device, &queue)), stride, element_size);
assert_eq!(dynamic.element_size(), element_size);
assert_eq!(dynamic.stride(), stride);
assert!(dynamic.stride() >= dynamic.element_size(), "stride is alignment-padded, never smaller than the element");
dynamic.write_element(count - 1, &vec![0u8; element_size as usize]);
});
}
}