use crate::{context::Has, graphics::gpu::bind_group::BindGroupBuilder, GameError, GameResult};
use super::{
context::GraphicsContext,
draw::{DrawParam, DrawUniforms, Std140DrawUniforms},
gpu::arc::{ArcBindGroup, ArcBindGroupLayout, ArcBuffer},
internal_canvas::InstanceArrayView,
transform_rect, Canvas, Draw, Drawable, Image, Mesh, Rect, WgpuContext,
};
use crevice::std140::AsStd140;
use std::{
collections::BTreeMap,
sync::{
atomic::{AtomicBool, AtomicUsize, Ordering::SeqCst},
Mutex,
},
};
const DEFAULT_CAPACITY: usize = 16;
#[derive(Debug)]
pub struct InstanceArray {
pub(crate) buffer: Mutex<ArcBuffer>,
pub(crate) indices: Mutex<ArcBuffer>,
pub(crate) bind_group: Mutex<ArcBindGroup>,
pub(crate) bind_layout: ArcBindGroupLayout,
pub(crate) image: Image,
pub(crate) ordered: bool,
dirty: AtomicBool,
capacity: AtomicUsize,
uniforms: Vec<Std140DrawUniforms>,
params: Vec<DrawParam>,
}
impl InstanceArray {
pub fn new(gfx: &impl Has<GraphicsContext>, image: impl Into<Option<Image>>) -> Self {
let gfx = gfx.retrieve();
InstanceArray::new_wgpu(
&gfx.wgpu,
gfx.instance_bind_layout.clone(),
image.into().unwrap_or_else(|| gfx.white_image.clone()),
DEFAULT_CAPACITY,
false,
)
}
pub fn new_ordered(gfx: &impl Has<GraphicsContext>, image: impl Into<Option<Image>>) -> Self {
let gfx = gfx.retrieve();
InstanceArray::new_wgpu(
&gfx.wgpu,
gfx.instance_bind_layout.clone(),
image.into().unwrap_or_else(|| gfx.white_image.clone()),
DEFAULT_CAPACITY,
true,
)
}
fn new_wgpu(
wgpu: &WgpuContext,
bind_layout: ArcBindGroupLayout,
image: Image,
capacity: usize,
ordered: bool,
) -> Self {
assert!(capacity > 0);
let buffer = ArcBuffer::new(wgpu.device.create_buffer(&wgpu::BufferDescriptor {
label: None,
size: DrawUniforms::std140_size_static() as u64 * capacity as u64,
usage: wgpu::BufferUsages::STORAGE
| wgpu::BufferUsages::COPY_DST
| wgpu::BufferUsages::COPY_SRC,
mapped_at_creation: false,
}));
let indices = ArcBuffer::new(wgpu.device.create_buffer(&wgpu::BufferDescriptor {
label: None,
size: if ordered {
std::mem::size_of::<u32>() as u64 * capacity as u64
} else {
4 },
usage: wgpu::BufferUsages::STORAGE
| wgpu::BufferUsages::COPY_SRC
| wgpu::BufferUsages::COPY_DST,
mapped_at_creation: false,
}));
let bind_group = BindGroupBuilder::new()
.buffer(
&buffer,
0,
wgpu::ShaderStages::VERTEX,
wgpu::BufferBindingType::Storage { read_only: true },
false,
None,
)
.buffer(
&indices,
0,
wgpu::ShaderStages::VERTEX,
wgpu::BufferBindingType::Storage { read_only: true },
false,
None,
);
let bind_group =
ArcBindGroup::new(wgpu.device.create_bind_group(&wgpu::BindGroupDescriptor {
label: None,
layout: &bind_layout,
entries: bind_group.entries(),
}));
let uniforms = Vec::with_capacity(capacity);
let params = Vec::with_capacity(capacity);
InstanceArray {
buffer: Mutex::new(buffer),
indices: Mutex::new(indices),
bind_group: Mutex::new(bind_group),
bind_layout,
image,
ordered,
dirty: AtomicBool::new(false),
capacity: AtomicUsize::new(capacity),
uniforms,
params,
}
}
pub fn set(&mut self, instances: impl IntoIterator<Item = DrawParam>) {
self.dirty.store(true, SeqCst);
self.params.clear();
self.params.extend(instances);
self.uniforms.clear();
self.uniforms.extend(
self.params
.iter()
.map(|x| DrawUniforms::from_param(x, None).as_std140()),
);
}
pub fn push(&mut self, instance: DrawParam) {
self.dirty.store(true, SeqCst);
self.uniforms
.push(DrawUniforms::from_param(&instance, None).as_std140());
self.params.push(instance);
}
pub fn update(&mut self, index: u32, instance: DrawParam) {
if let Some((uniform, param)) = self
.uniforms
.get_mut(index as usize)
.and_then(|x| Some((x, self.params.get_mut(index as usize)?)))
{
self.dirty.store(true, SeqCst);
*uniform = DrawUniforms::from_param(&instance, None).as_std140();
*param = instance;
}
}
pub fn clear(&mut self) {
self.uniforms.clear();
self.params.clear();
}
#[inline]
pub fn is_dirty(&self) -> bool {
self.dirty.load(SeqCst)
}
#[inline]
pub fn instances(&self) -> &[DrawParam] {
&self.params
}
pub(crate) fn flush_wgpu(&self, wgpu: &WgpuContext) -> GameResult {
if !self.dirty.load(SeqCst) {
return Ok(());
} else {
self.dirty.store(false, SeqCst);
}
let len = self.uniforms.len();
let mut resized = InstanceArray::new_wgpu(
wgpu,
self.bind_layout.clone(),
self.image.clone(),
len,
self.ordered,
);
*self.buffer.lock().map_err(|_| GameError::LockError)? =
resized.buffer.get_mut().unwrap().clone();
*self.indices.lock().map_err(|_| GameError::LockError)? =
resized.indices.get_mut().unwrap().clone();
*self.bind_group.lock().map_err(|_| GameError::LockError)? =
resized.bind_group.get_mut().unwrap().clone();
self.capacity.store(len, SeqCst);
wgpu.queue.write_buffer(
&self.buffer.lock().unwrap(),
0,
bytemuck::cast_slice(self.uniforms.as_slice()),
);
if self.ordered {
let mut layers = BTreeMap::<_, Vec<_>>::new();
for (i, param) in self.params.iter().enumerate() {
layers.entry(param.z).or_default().push(i as u32);
}
let indices = layers.into_values().flatten().collect::<Vec<_>>();
wgpu.queue.write_buffer(
&self.indices.lock().unwrap(),
0,
bytemuck::cast_slice(indices.as_slice()),
);
}
Ok(())
}
pub fn resize(&mut self, gfx: &impl Has<GraphicsContext>, new_capacity: usize) {
assert!(new_capacity > 0);
let gfx: &GraphicsContext = gfx.retrieve();
let resized = InstanceArray::new_wgpu(
&gfx.wgpu,
self.bind_layout.clone(),
self.image.clone(),
new_capacity,
self.ordered,
);
self.buffer = resized.buffer;
self.indices = resized.indices;
self.bind_group = resized.bind_group;
self.capacity.store(new_capacity, SeqCst);
self.dirty.store(true, SeqCst);
self.uniforms.truncate(new_capacity);
self.params.truncate(new_capacity);
self.uniforms.reserve(new_capacity - self.uniforms.len());
self.params.reserve(new_capacity - self.params.len());
}
#[inline]
pub fn image(&self) -> Image {
self.image.clone()
}
#[inline]
pub fn capacity(&self) -> usize {
self.capacity.load(SeqCst)
}
pub fn dimensions_meshed(&self, gfx: &impl Has<GraphicsContext>, mesh: &Mesh) -> Option<Rect> {
if self.params.is_empty() {
return None;
}
let dimensions = mesh.dimensions(gfx)?;
self.params
.iter()
.map(|¶m| transform_rect(dimensions, param))
.fold(None, |acc: Option<Rect>, rect| {
Some(if let Some(acc) = acc {
acc.combine_with(rect)
} else {
rect
})
})
}
}
impl Drawable for InstanceArray {
fn draw(&self, canvas: &mut Canvas, param: impl Into<DrawParam>) {
if self.instances().is_empty() {
return;
}
self.flush_wgpu(&canvas.wgpu).unwrap();
canvas.push_draw(
Draw::MeshInstances {
mesh: canvas.default_resources().mesh.clone(),
instances: InstanceArrayView::from_instances(self).unwrap(),
scale: true,
},
param.into(),
);
}
fn dimensions(&self, gfx: &impl Has<GraphicsContext>) -> Option<Rect> {
let gfx = gfx.retrieve();
if self.params.is_empty() {
return None;
}
let dimensions = self.image.dimensions(gfx)?;
self.params
.iter()
.map(|¶m| transform_rect(dimensions, param))
.fold(None, |acc: Option<Rect>, rect| {
Some(if let Some(acc) = acc {
acc.combine_with(rect)
} else {
rect
})
})
}
}