summaryrefslogtreecommitdiff
path: root/render/src/instance.rs
diff options
context:
space:
mode:
Diffstat (limited to 'render/src/instance.rs')
-rw-r--r--render/src/instance.rs167
1 files changed, 167 insertions, 0 deletions
diff --git a/render/src/instance.rs b/render/src/instance.rs
new file mode 100644
index 0000000..e346cae
--- /dev/null
+++ b/render/src/instance.rs
@@ -0,0 +1,167 @@
+use std::mem::size_of;
+
+use bytemuck::{Pod, Zeroable};
+
+/// The ID for an instance
+#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
+pub struct InstanceId(usize);
+
+/// A sprite, that can be used by the alligator shader
+#[repr(C)]
+#[derive(Copy, Clone, Debug, PartialEq, Pod, Zeroable)]
+pub struct Instance {
+ /// Position on the screen
+ pub position: [f32; 2],
+ /// Relative size
+ pub size: [f32; 2],
+ /// The location of the texture in the texture atlas
+ pub texture_coordinates: [f32; 2],
+ /// The size of the sprite's texture
+ pub texture_size: [f32; 2],
+ /// The index of the texture atlas to use
+ pub texture_atlas_index: u32,
+ /// Rotation, in radians
+ pub rotation: f32,
+ /// z-index
+ pub z_index: f32,
+}
+
+impl Default for Instance {
+ fn default() -> Self {
+ Self {
+ position: [0.0; 2],
+ size: [1.0; 2],
+ rotation: 0.0,
+ z_index: 0.0,
+ texture_coordinates: [0.0; 2],
+ texture_size: [1.0; 2],
+ texture_atlas_index: 0,
+ }
+ }
+}
+
+impl Instance {
+ // whenever this is updated, please also update `sprite.wgsl`
+ const ATTRIBUTES: [wgpu::VertexAttribute; 7] = wgpu::vertex_attr_array![
+ 1 => Float32x2, 2 => Float32x2, 3 => Float32x2, 4 => Float32x2,
+ 5 => Uint32, 6 => Float32, 7 => Float32
+ ];
+
+ pub(crate) fn desc<'a>() -> wgpu::VertexBufferLayout<'a> {
+ // make sure these two don't conflict
+ debug_assert_eq!(
+ Self::ATTRIBUTES[0].shader_location as usize,
+ crate::Vertex::ATTRIBUTES.len()
+ );
+ wgpu::VertexBufferLayout {
+ array_stride: size_of::<Self>() as wgpu::BufferAddress,
+ step_mode: wgpu::VertexStepMode::Instance,
+ attributes: &Self::ATTRIBUTES,
+ }
+ }
+}
+
+/// A buffer of sprites, for both CPU and GPU memory
+pub struct InstanceBuffer {
+ instances: Vec<Instance>,
+ instance_buffer: wgpu::Buffer,
+ instance_buffer_size: usize,
+}
+
+fn create_buffer(device: &wgpu::Device, instances: &Vec<Instance>) -> wgpu::Buffer {
+ device.create_buffer(&wgpu::BufferDescriptor {
+ label: Some("Sprite Instance Buffer"),
+ size: (instances.capacity() * size_of::<Instance>()) as wgpu::BufferAddress,
+ usage: wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST,
+ mapped_at_creation: false,
+ })
+}
+
+impl InstanceBuffer {
+ /// Create a new buffer with the given capacity
+ pub(crate) fn new(device: &wgpu::Device, capacity: usize) -> Self {
+ let instances = Vec::with_capacity(capacity);
+ let instance_buffer_size = instances.capacity();
+ let instance_buffer = create_buffer(device, &instances);
+
+ Self {
+ instances,
+ instance_buffer,
+ instance_buffer_size,
+ }
+ }
+
+ /// The number of sprites
+ pub fn len(&self) -> u32 {
+ self.instances
+ .len()
+ .try_into()
+ .expect("expected less than 3 billion instances")
+ }
+
+ /// Returns `true` if there are no sprites
+ pub fn is_empty(&self) -> bool {
+ self.instances.is_empty()
+ }
+
+ /// The capacity of the buffer
+ pub const fn buffer_size(&self) -> usize {
+ self.instance_buffer_size
+ }
+
+ /// Get a slice containing the entire buffer
+ pub(crate) fn buffer_slice(&self) -> wgpu::BufferSlice {
+ self.instance_buffer.slice(..)
+ }
+
+ /// Add a new sprite. The new sprite's `InstanceId` is returned. This ID
+ /// becomes invalid if the buffer is cleared.
+ pub fn push_instance(&mut self, instance: Instance) -> InstanceId {
+ let index = self.instances.len();
+ self.instances.push(instance);
+ InstanceId(index)
+ }
+
+ /// Get a specific instance
+ pub fn get_instance(&self, id: InstanceId) -> Option<&Instance> {
+ self.instances.get(id.0)
+ }
+
+ /// Get a mutable reference to a specific sprite
+ pub fn get_instance_mut(&mut self, id: InstanceId) -> Option<&mut Instance> {
+ self.instances.get_mut(id.0)
+ }
+
+ /// Clear the instance buffer. This invalidates all `InstanceId`'s
+ pub fn clear(&mut self) {
+ self.instances.clear();
+ }
+
+ /// Increase the capacity of the buffer
+ fn expand_buffer(&mut self, device: &wgpu::Device) {
+ self.instance_buffer_size = self.instances.capacity();
+ self.instance_buffer = create_buffer(device, &self.instances);
+ }
+
+ /// Fill the GPU buffer with the sprites in the CPU buffer.
+ #[profiling::function]
+ pub(crate) fn fill_buffer(&mut self, device: &wgpu::Device, queue: &wgpu::Queue) {
+ if self.instances.len() > self.instance_buffer_size {
+ self.expand_buffer(device);
+ }
+
+ // the instances must be sorted by z-index before being handed to the GPU
+ let sorted = {
+ profiling::scope!("depth sorting");
+ let mut sorted = self.instances.clone();
+ sorted.sort_by(|a, b| a.z_index.total_cmp(&b.z_index));
+ sorted
+ };
+
+ queue.write_buffer(
+ &self.instance_buffer,
+ 0 as wgpu::BufferAddress,
+ bytemuck::cast_slice(&sorted),
+ );
+ }
+}