use piet_hardware::Vertex;
use std::cell::{Ref, RefCell, RefMut};
use std::hash::{Hash, Hasher};
use std::mem;
use std::rc::Rc;
#[derive(Debug, Clone)]
pub(crate) struct WgpuVertexBuffer(Rc<VertexBufferInner>);
impl PartialEq for WgpuVertexBuffer {
fn eq(&self, other: &Self) -> bool {
Rc::ptr_eq(&self.0, &other.0)
}
}
impl Eq for WgpuVertexBuffer {}
impl Hash for WgpuVertexBuffer {
fn hash<H: Hasher>(&self, state: &mut H) {
Rc::as_ptr(&self.0).hash(state);
}
}
#[derive(Debug)]
struct VertexBufferInner {
vertex_buffer: RefCell<Buffer>,
index_buffer: RefCell<Buffer>,
}
impl WgpuVertexBuffer {
pub(crate) fn new([id1, id2]: [usize; 2], device: &wgpu::Device) -> Self {
const INITIAL_VERTEX_BUFFER_SIZE: usize = 1024 * mem::size_of::<Vertex>();
const INITIAL_INDEX_BUFFER_SIZE: usize = 1024 * mem::size_of::<u32>();
let vertex_buffer = Buffer::new(
id1,
device,
wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST,
INITIAL_VERTEX_BUFFER_SIZE,
"vertex",
);
let index_buffer = Buffer::new(
id2,
device,
wgpu::BufferUsages::INDEX | wgpu::BufferUsages::COPY_DST,
INITIAL_INDEX_BUFFER_SIZE,
"index",
);
WgpuVertexBuffer(Rc::new(VertexBufferInner {
vertex_buffer: RefCell::new(vertex_buffer),
index_buffer: RefCell::new(index_buffer),
}))
}
pub(crate) fn borrow_vertex_buffer(&self) -> Ref<'_, Buffer> {
self.0.vertex_buffer.borrow()
}
pub(crate) fn borrow_index_buffer(&self) -> Ref<'_, Buffer> {
self.0.index_buffer.borrow()
}
pub(crate) fn borrow_vertex_buffer_mut(&self) -> RefMut<'_, Buffer> {
self.0.vertex_buffer.borrow_mut()
}
pub(crate) fn borrow_index_buffer_mut(&self) -> RefMut<'_, Buffer> {
self.0.index_buffer.borrow_mut()
}
}
#[derive(Debug)]
pub(crate) struct Buffer {
id: usize,
capacity: usize,
last_capacity: usize,
start_cursor: usize,
end_cursor: usize,
usage: wgpu::BufferUsages,
buffer_id: &'static str,
buffer: BufferCollection,
}
#[derive(Debug)]
enum BufferCollection {
Single(Rc<wgpu::Buffer>),
List(Vec<Rc<wgpu::Buffer>>),
Hole,
}
impl BufferCollection {
fn get(&self, i: usize) -> Option<&Rc<wgpu::Buffer>> {
match (self, i) {
(BufferCollection::Single(buffer), 0) => Some(buffer),
(BufferCollection::List(buffers), i) => buffers.get(i),
_ => None,
}
}
fn last(&self) -> Option<&Rc<wgpu::Buffer>> {
match self {
BufferCollection::Single(buffer) => Some(buffer),
BufferCollection::List(buffers) => buffers.last(),
_ => None,
}
}
fn last_mut(&mut self) -> Option<&mut Rc<wgpu::Buffer>> {
match self {
BufferCollection::Single(buffer) => Some(buffer),
BufferCollection::List(buffers) => buffers.last_mut(),
_ => None,
}
}
fn push(&mut self, buffer: wgpu::Buffer) {
let buffer = Rc::new(buffer);
match mem::replace(self, Self::Hole) {
Self::Hole => *self = Self::Single(buffer),
Self::Single(old_buffer) => {
tracing::debug!("using list-based buffering strategy");
*self = Self::List(vec![old_buffer, buffer])
}
Self::List(mut buffers) => {
buffers.push(buffer);
*self = Self::List(buffers);
}
}
}
fn len(&self) -> usize {
match self {
BufferCollection::Single(_) => 1,
BufferCollection::List(buffers) => buffers.len(),
_ => 0,
}
}
}
#[derive(Debug, Clone, Copy)]
pub(crate) struct BufferSlice {
buffer_index: usize,
range: (u64, u64),
}
impl BufferSlice {
pub(crate) fn range(&self) -> std::ops::Range<u64> {
self.range.0..self.range.1
}
}
impl Buffer {
fn create_buffer(&self, dev: &wgpu::Device, len: usize) -> wgpu::Buffer {
dev.create_buffer(&wgpu::BufferDescriptor {
label: Some(&format!("piet-wgpu {} buffer {}", self.buffer_id, self.id)),
usage: self.usage,
size: len.try_into().expect("buffer too large"),
mapped_at_creation: false,
})
}
pub(crate) fn write_buffer(&mut self, device: &wgpu::Device, queue: &wgpu::Queue, data: &[u8]) {
let remaining_capacity = self.last_capacity - self.end_cursor;
if remaining_capacity < data.len() {
let new_capacity = data
.len()
.checked_add(remaining_capacity)
.map(|len| len.next_power_of_two())
.expect("buffer too large");
let new_buffer = self.create_buffer(device, new_capacity);
if self.start_cursor == 0 {
*self.buffer.last_mut().unwrap() = Rc::new(new_buffer);
self.capacity -= self.last_capacity;
} else {
self.buffer.push(new_buffer);
self.start_cursor = 0;
self.end_cursor = 0;
}
self.last_capacity = new_capacity;
self.capacity += new_capacity;
}
queue.write_buffer(
self.buffer.last().unwrap(),
self.start_cursor.try_into().expect("buffer too large"),
data,
);
self.end_cursor = self.start_cursor + data.len();
tracing::debug!(
"Wrote to {} buffer from {} to {}",
self.buffer_id,
self.start_cursor,
self.end_cursor
);
}
pub(crate) fn pop_slice(&mut self) -> BufferSlice {
let slice = BufferSlice {
buffer_index: self.buffer.len() - 1,
range: (self.start_cursor as u64, self.end_cursor as u64),
};
tracing::debug!(slice=?slice, "Popped {} buffer slice", self.buffer_id);
self.start_cursor = self.end_cursor;
slice
}
pub(crate) fn clear(&mut self, device: &wgpu::Device) {
self.start_cursor = 0;
self.end_cursor = 0;
if matches!(self.buffer, BufferCollection::List(..)) {
let desired_capacity = self.capacity.next_power_of_two();
tracing::debug!("Resizing {} buffer to {}", self.buffer_id, desired_capacity);
let new_buffer = self.create_buffer(device, desired_capacity);
self.buffer = BufferCollection::Single(Rc::new(new_buffer));
self.capacity = desired_capacity;
self.last_capacity = desired_capacity;
}
}
fn new(
id: usize,
device: &wgpu::Device,
usage: wgpu::BufferUsages,
starting_size: usize,
buffer_id: &'static str,
) -> Self {
let starting_size = starting_size.next_power_of_two();
let mut this = Self {
id,
capacity: starting_size,
last_capacity: starting_size,
start_cursor: 0,
end_cursor: 0,
buffer_id,
usage,
buffer: BufferCollection::Hole,
};
this.buffer = BufferCollection::Single(Rc::new(this.create_buffer(device, starting_size)));
this
}
pub(crate) fn get(&self, slice: BufferSlice) -> Option<&Rc<wgpu::Buffer>> {
self.buffer.get(slice.buffer_index)
}
}