maplibre-rs/src/render/buffer_pool.rs

639 lines
21 KiB
Rust

use std::collections::{btree_map, BTreeMap, HashSet, VecDeque};
use std::fmt::Debug;
use std::marker::PhantomData;
use std::mem::size_of;
use std::ops::Range;
use style_spec::layer::StyleLayer;
use wgpu::BufferAddress;
use crate::coords::{Quadkey, WorldTileCoords};
use crate::tessellation::OverAlignedVertexBuffer;
pub trait Queue<B> {
fn write_buffer(&self, buffer: &B, offset: wgpu::BufferAddress, data: &[u8]);
}
impl Queue<wgpu::Buffer> for wgpu::Queue {
fn write_buffer(&self, buffer: &wgpu::Buffer, offset: wgpu::BufferAddress, data: &[u8]) {
self.write_buffer(buffer, offset, data)
}
}
/// This is inspired by the memory pool in Vulkan documented
/// [here](https://gpuopen-librariesandsdks.github.io/VulkanMemoryAllocator/html/custom_memory_pools.html).
#[derive(Debug)]
pub struct BufferPool<Q, B, V, I, M, FM> {
vertices: BackingBuffer<B>,
indices: BackingBuffer<B>,
layer_metadata: BackingBuffer<B>,
feature_metadata: BackingBuffer<B>,
index: RingIndex,
phantom_v: PhantomData<V>,
phantom_i: PhantomData<I>,
phantom_q: PhantomData<Q>,
phantom_m: PhantomData<M>,
phantom_fm: PhantomData<FM>,
}
#[derive(Debug)]
enum BackingBufferType {
Vertices,
Indices,
Metadata,
FeatureMetadata,
}
impl<Q: Queue<B>, B, V: bytemuck::Pod, I: bytemuck::Pod, TM: bytemuck::Pod, FM: bytemuck::Pod>
BufferPool<Q, B, V, I, TM, FM>
{
pub fn new(
vertices: BackingBufferDescriptor<B>,
indices: BackingBufferDescriptor<B>,
layer_metadata: BackingBufferDescriptor<B>,
feature_metadata: BackingBufferDescriptor<B>,
) -> Self {
Self {
vertices: BackingBuffer::new(
vertices.buffer,
vertices.inner_size,
BackingBufferType::Vertices,
),
indices: BackingBuffer::new(
indices.buffer,
indices.inner_size,
BackingBufferType::Indices,
),
layer_metadata: BackingBuffer::new(
layer_metadata.buffer,
layer_metadata.inner_size,
BackingBufferType::Metadata,
),
feature_metadata: BackingBuffer::new(
feature_metadata.buffer,
feature_metadata.inner_size,
BackingBufferType::FeatureMetadata,
),
index: RingIndex::new(),
phantom_v: Default::default(),
phantom_i: Default::default(),
phantom_q: Default::default(),
phantom_m: Default::default(),
phantom_fm: Default::default(),
}
}
#[cfg(test)]
fn available_space(&self, typ: BackingBufferType) -> wgpu::BufferAddress {
let gap = match typ {
BackingBufferType::Vertices => &self.vertices,
BackingBufferType::Indices => &self.indices,
BackingBufferType::Metadata => &self.layer_metadata,
BackingBufferType::FeatureMetadata => &self.feature_metadata,
}
.find_largest_gap(&self.index);
gap.end - gap.start
}
pub fn vertices(&self) -> &B {
&self.vertices.inner
}
pub fn indices(&self) -> &B {
&self.indices.inner
}
pub fn metadata(&self) -> &B {
&self.layer_metadata.inner
}
pub fn feature_metadata(&self) -> &B {
&self.feature_metadata.inner
}
/// The VertexBuffers can contain padding elements. Not everything from a VertexBuffers is useable.
/// The function returns the `bytes` and `aligned_bytes`. See [`OverAlignedVertexBuffer`].
fn align(
stride: wgpu::BufferAddress,
elements: wgpu::BufferAddress,
usable_elements: wgpu::BufferAddress,
) -> (BufferAddress, BufferAddress) {
let bytes = elements * stride;
let usable_bytes = (usable_elements * stride) as wgpu::BufferAddress;
let align = wgpu::COPY_BUFFER_ALIGNMENT;
let padding = (align - usable_bytes % align) % align;
let aligned_bytes = usable_bytes + padding;
(bytes, aligned_bytes)
}
pub fn get_loaded_layers_at(&self, coords: &WorldTileCoords) -> Option<HashSet<&str>> {
self.index.get_layers(coords).map(|layers| {
layers
.iter()
.map(|entry| entry.style_layer.source_layer.as_ref().unwrap().as_str())
.collect()
})
}
/// Allocates
/// * `geometry`
/// * `layer_metadata` and
/// * `feature_metadata` for a layer. This function is able to dynamically evict layers if there
/// is not enough space available.
#[tracing::instrument(skip_all)]
pub fn allocate_layer_geometry(
&mut self,
queue: &Q,
coords: WorldTileCoords,
style_layer: StyleLayer,
geometry: &OverAlignedVertexBuffer<V, I>,
layer_metadata: TM,
feature_metadata: &[FM],
) {
let vertices_stride = size_of::<V>() as wgpu::BufferAddress;
let indices_stride = size_of::<I>() as wgpu::BufferAddress;
let layer_metadata_stride = size_of::<TM>() as wgpu::BufferAddress;
let feature_metadata_stride = size_of::<FM>() as wgpu::BufferAddress;
let (vertices_bytes, aligned_vertices_bytes) = Self::align(
vertices_stride,
geometry.buffer.vertices.len() as BufferAddress,
geometry.buffer.vertices.len() as BufferAddress,
);
let (indices_bytes, aligned_indices_bytes) = Self::align(
indices_stride,
geometry.buffer.indices.len() as BufferAddress,
geometry.usable_indices as BufferAddress,
);
let (layer_metadata_bytes, aligned_layer_metadata_bytes) =
Self::align(layer_metadata_stride, 1, 1);
let (feature_metadata_bytes, aligned_feature_metadata_bytes) = Self::align(
feature_metadata_stride,
feature_metadata.len() as BufferAddress,
feature_metadata.len() as BufferAddress,
);
if feature_metadata_bytes != aligned_feature_metadata_bytes {
// FIXME: align if not aligned?
panic!(
"feature_metadata is not aligned. This should not happen as long as size_of::<FM>() is a multiple of the alignment."
)
}
let maybe_entry = IndexEntry {
coords,
style_layer,
buffer_vertices: self.vertices.make_room(vertices_bytes, &mut self.index),
buffer_indices: self.indices.make_room(indices_bytes, &mut self.index),
usable_indices: geometry.usable_indices as u32,
buffer_layer_metadata: self
.layer_metadata
.make_room(layer_metadata_bytes, &mut self.index),
buffer_feature_metadata: self
.feature_metadata
.make_room(feature_metadata_bytes, &mut self.index),
};
// write_buffer() is the preferred method for WASM: https://toji.github.io/webgpu-best-practices/buffer-uploads.html#when-in-doubt-writebuffer
queue.write_buffer(
&self.vertices.inner,
maybe_entry.buffer_vertices.start,
&bytemuck::cast_slice(&geometry.buffer.vertices)[0..aligned_vertices_bytes as usize],
);
queue.write_buffer(
&self.indices.inner,
maybe_entry.buffer_indices.start,
&bytemuck::cast_slice(&geometry.buffer.indices)[0..aligned_indices_bytes as usize],
);
queue.write_buffer(
&self.layer_metadata.inner,
maybe_entry.buffer_layer_metadata.start,
&bytemuck::cast_slice(&[layer_metadata])[0..aligned_layer_metadata_bytes as usize],
);
queue.write_buffer(
&self.feature_metadata.inner,
maybe_entry.buffer_feature_metadata.start,
&bytemuck::cast_slice(feature_metadata)[0..aligned_feature_metadata_bytes as usize],
);
self.index.push_back(maybe_entry);
}
#[tracing::instrument(skip_all)]
pub fn update_layer_metadata(&self, queue: &Q, entry: &IndexEntry, layer_metadata: TM) {
let layer_metadata_stride = size_of::<TM>() as wgpu::BufferAddress; // TODO: deduplicate
let (layer_metadata_bytes, aligned_layer_metadata_bytes) =
Self::align(layer_metadata_stride, 1, 1);
if entry.buffer_layer_metadata.end - entry.buffer_layer_metadata.start
!= layer_metadata_bytes
{
panic!("Updated layer metadata has wrong size!");
}
queue.write_buffer(
&self.layer_metadata.inner,
entry.buffer_layer_metadata.start,
&bytemuck::cast_slice(&[layer_metadata])[0..aligned_layer_metadata_bytes as usize],
);
}
#[tracing::instrument(skip_all)]
pub fn update_feature_metadata(&self, queue: &Q, entry: &IndexEntry, feature_metadata: &[FM]) {
let feature_metadata_stride = size_of::<FM>() as wgpu::BufferAddress; // TODO: deduplicate
let (feature_metadata_bytes, aligned_feature_metadata_bytes) = Self::align(
feature_metadata_stride,
feature_metadata.len() as BufferAddress,
feature_metadata.len() as BufferAddress,
);
if entry.buffer_feature_metadata.end - entry.buffer_feature_metadata.start
!= feature_metadata_bytes
{
panic!("Updated feature metadata has wrong size!");
}
if feature_metadata_bytes != aligned_feature_metadata_bytes {
// FIXME: align if not aligned?
panic!(
"feature_metadata is not aligned. This should not happen as long as size_of::<FM>() is a multiple of the alignment."
)
}
queue.write_buffer(
&self.feature_metadata.inner,
entry.buffer_feature_metadata.start,
&bytemuck::cast_slice(feature_metadata)[0..aligned_feature_metadata_bytes as usize],
);
}
pub fn index(&self) -> &RingIndex {
&self.index
}
}
pub struct BackingBufferDescriptor<B> {
/// The buffer which is used
pub buffer: B,
/// The size of buffer
inner_size: wgpu::BufferAddress,
}
impl<B> BackingBufferDescriptor<B> {
pub fn new(buffer: B, inner_size: wgpu::BufferAddress) -> Self {
Self { buffer, inner_size }
}
}
#[derive(Debug)]
struct BackingBuffer<B> {
/// The internal structure which is used for storage
inner: B,
/// The size of the `inner` buffer
inner_size: wgpu::BufferAddress,
typ: BackingBufferType,
}
impl<B> BackingBuffer<B> {
fn new(inner: B, inner_size: wgpu::BufferAddress, typ: BackingBufferType) -> Self {
Self {
inner,
inner_size,
typ,
}
}
fn make_room(
&mut self,
new_data: wgpu::BufferAddress,
index: &mut RingIndex,
) -> Range<wgpu::BufferAddress> {
if new_data > self.inner_size {
panic!("can not allocate because backing buffers are too small")
}
let mut available_gap = self.find_largest_gap(index);
while new_data > available_gap.end - available_gap.start {
// no more space, we need to evict items
if index.pop_front().is_some() {
available_gap = self.find_largest_gap(index);
} else {
panic!("evicted even though index is empty")
}
}
available_gap.start..available_gap.start + new_data
}
fn find_largest_gap(&self, index: &RingIndex) -> Range<wgpu::BufferAddress> {
let start = index.front().map(|first| match self.typ {
BackingBufferType::Vertices => first.buffer_vertices.start,
BackingBufferType::Indices => first.buffer_indices.start,
BackingBufferType::Metadata => first.buffer_layer_metadata.start,
BackingBufferType::FeatureMetadata => first.buffer_feature_metadata.start,
});
let end = index.back().map(|first| match self.typ {
BackingBufferType::Vertices => first.buffer_vertices.end,
BackingBufferType::Indices => first.buffer_indices.end,
BackingBufferType::Metadata => first.buffer_layer_metadata.end,
BackingBufferType::FeatureMetadata => first.buffer_feature_metadata.end,
});
if let Some(start) = start {
if let Some(end) = end {
if end > start {
// we haven't wrapped yet in the ring buffer
let gap_from_start = 0..start; // gap from beginning to first entry
let gap_to_end = end..self.inner_size;
if gap_to_end.end - gap_to_end.start > gap_from_start.end - gap_from_start.start
{
gap_to_end
} else {
gap_from_start
}
} else {
// we already wrapped in the ring buffer
// we choose the gab between the two
end..start
}
} else {
unreachable!()
}
} else {
0..self.inner_size
}
}
}
#[derive(Debug)]
pub struct IndexEntry {
pub coords: WorldTileCoords,
pub style_layer: StyleLayer,
// Range of bytes within the backing buffer for vertices
buffer_vertices: Range<wgpu::BufferAddress>,
// Range of bytes within the backing buffer for indices
buffer_indices: Range<wgpu::BufferAddress>,
// Range of bytes within the backing buffer for metadata
buffer_layer_metadata: Range<wgpu::BufferAddress>,
// Range of bytes within the backing buffer for feature metadata
buffer_feature_metadata: Range<wgpu::BufferAddress>,
// Amount of actually usable indices. Each index has the size/format `IndexDataType`.
// Can be lower than size(buffer_indices) / indices_stride because of alignment.
usable_indices: u32,
}
impl IndexEntry {
pub fn indices_range(&self) -> Range<u32> {
0..self.usable_indices
}
pub fn indices_buffer_range(&self) -> Range<wgpu::BufferAddress> {
self.buffer_indices.clone()
}
pub fn vertices_buffer_range(&self) -> Range<wgpu::BufferAddress> {
self.buffer_vertices.clone()
}
pub fn layer_metadata_buffer_range(&self) -> Range<wgpu::BufferAddress> {
self.buffer_layer_metadata.clone()
}
pub fn feature_metadata_buffer_range(&self) -> Range<wgpu::BufferAddress> {
self.buffer_feature_metadata.clone()
}
}
#[derive(Debug)]
pub struct RingIndex {
tree_index: BTreeMap<Quadkey, VecDeque<IndexEntry>>,
linear_index: VecDeque<Quadkey>,
}
impl RingIndex {
pub fn new() -> Self {
Self {
tree_index: Default::default(),
linear_index: Default::default(),
}
}
pub fn front(&self) -> Option<&IndexEntry> {
self.linear_index
.front()
.and_then(|key| self.tree_index.get(key).and_then(|entries| entries.front()))
}
pub fn back(&self) -> Option<&IndexEntry> {
self.linear_index
.back()
.and_then(|key| self.tree_index.get(key).and_then(|entries| entries.back()))
}
pub fn get_layers(&self, coords: &WorldTileCoords) -> Option<&VecDeque<IndexEntry>> {
coords
.build_quad_key()
.and_then(|key| self.tree_index.get(&key))
}
pub fn get_layers_fallback(&self, coords: &WorldTileCoords) -> Option<&VecDeque<IndexEntry>> {
let mut current = *coords;
loop {
if let Some(entries) = self.get_layers(&current) {
return Some(entries);
} else if let Some(parent) = current.get_parent() {
current = parent
} else {
return None;
}
}
}
pub fn iter(&self) -> impl Iterator<Item = impl Iterator<Item = &IndexEntry>> + '_ {
self.linear_index
.iter()
.flat_map(|key| self.tree_index.get(key).map(|entries| entries.iter()))
}
fn pop_front(&mut self) -> Option<IndexEntry> {
if let Some(entries) = self
.linear_index
.pop_front()
.and_then(|key| self.tree_index.get_mut(&key))
{
entries.pop_front()
} else {
None
}
}
fn push_back(&mut self, entry: IndexEntry) {
if let Some(key) = entry.coords.build_quad_key() {
match self.tree_index.entry(key) {
btree_map::Entry::Vacant(index_entry) => {
index_entry.insert(VecDeque::from([entry]));
}
btree_map::Entry::Occupied(mut index_entry) => {
index_entry.get_mut().push_back(entry);
}
}
self.linear_index.push_back(key)
} else {
unreachable!() // TODO handle
}
}
}
#[cfg(test)]
mod tests {
use lyon::tessellation::VertexBuffers;
use style_spec::layer::StyleLayer;
use wgpu::BufferAddress;
use crate::render::buffer_pool::{
BackingBufferDescriptor, BackingBufferType, BufferPool, Queue,
};
#[derive(Debug)]
struct TestBuffer {
size: BufferAddress,
}
struct TestQueue;
impl Queue<TestBuffer> for TestQueue {
fn write_buffer(&self, buffer: &TestBuffer, offset: BufferAddress, data: &[u8]) {
if offset + data.len() as BufferAddress > buffer.size {
panic!("write out of bounds");
}
}
}
#[repr(C)]
#[derive(Default, Copy, Clone, bytemuck_derive::Pod, bytemuck_derive::Zeroable)]
struct TestVertex {
data: [u8; 24],
}
fn create_48byte() -> Vec<TestVertex> {
vec![TestVertex::default(), TestVertex::default()]
}
fn create_24byte() -> Vec<TestVertex> {
vec![TestVertex::default()]
}
#[test]
fn test_allocate() {
let mut pool: BufferPool<TestQueue, TestBuffer, TestVertex, u32, u32, u32> =
BufferPool::new(
BackingBufferDescriptor::new(TestBuffer { size: 128 }, 128),
BackingBufferDescriptor::new(TestBuffer { size: 128 }, 128),
BackingBufferDescriptor::new(TestBuffer { size: 128 }, 128),
BackingBufferDescriptor::new(TestBuffer { size: 128 }, 128),
);
let queue = TestQueue {};
let style_layer = StyleLayer::default();
let mut data48bytes = VertexBuffers::new();
data48bytes.vertices.append(&mut create_48byte());
data48bytes.indices.append(&mut vec![1, 2, 3, 4]);
let data48bytes_aligned = data48bytes.into();
let mut data24bytes = VertexBuffers::new();
data24bytes.vertices.append(&mut create_24byte());
data24bytes.indices.append(&mut vec![1, 2, 3, 4]);
let data24bytes_aligned = data24bytes.into();
for _ in 0..2 {
pool.allocate_layer_geometry(
&queue,
(0, 0, 0).into(),
style_layer.clone(),
&data48bytes_aligned,
2,
&[],
);
}
assert_eq!(
128 - 2 * 48,
pool.available_space(BackingBufferType::Vertices)
);
pool.allocate_layer_geometry(
&queue,
(0, 0, 0).into(),
style_layer.clone(),
&data24bytes_aligned,
2,
&[],
);
assert_eq!(
128 - 2 * 48 - 24,
pool.available_space(BackingBufferType::Vertices)
);
println!("{:?}", &pool.index);
pool.allocate_layer_geometry(
&queue,
(0, 0, 0).into(),
style_layer.clone(),
&data24bytes_aligned,
2,
&[],
);
// appended now at the beginning
println!("{:?}", &pool.index);
assert_eq!(24, pool.available_space(BackingBufferType::Vertices));
pool.allocate_layer_geometry(
&queue,
(0, 0, 0).into(),
style_layer.clone(),
&data24bytes_aligned,
2,
&[],
);
println!("{:?}", &pool.index);
assert_eq!(0, pool.available_space(BackingBufferType::Vertices));
pool.allocate_layer_geometry(
&queue,
(0, 0, 0).into(),
style_layer.clone(),
&data24bytes_aligned,
2,
&[],
);
println!("{:?}", &pool.index);
assert_eq!(24, pool.available_space(BackingBufferType::Vertices));
pool.allocate_layer_geometry(
&queue,
(0, 0, 0).into(),
style_layer,
&data24bytes_aligned,
2,
&[],
);
println!("{:?}", &pool.index);
assert_eq!(0, pool.available_space(BackingBufferType::Vertices));
}
}