use std::collections::HashMap;
use wgpu::util::DeviceExt;
use crate::resources::SkinWeights;
use crate::resources::ViewportGpuResources;
use crate::resources::mesh_store::MeshId;
#[repr(C)]
#[derive(Copy, Clone, Debug, bytemuck::Pod, bytemuck::Zeroable)]
pub(crate) struct PackedSkinVertex {
pub weights: [f32; 4],
pub joints_01: u32,
pub joints_23: u32,
pub _pad: [u32; 2],
}
pub(crate) struct InstancePalette {
pub buffer: wgpu::Buffer,
pub bind_group: wgpu::BindGroup,
pub joint_capacity: u32,
}
pub(crate) struct MeshSkinning {
pub weights_buffer: wgpu::Buffer,
pub vertex_count: u32,
pub instances: HashMap<u32, InstancePalette>,
}
pub(crate) struct SkinningState {
pub bind_group_layout: wgpu::BindGroupLayout,
pub meshes: HashMap<MeshId, MeshSkinning>,
}
impl SkinningState {
pub fn new(device: &wgpu::Device) -> Self {
let bind_group_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
label: Some("skin_bgl"),
entries: &[
wgpu::BindGroupLayoutEntry {
binding: 0,
visibility: wgpu::ShaderStages::VERTEX,
ty: wgpu::BindingType::Buffer {
ty: wgpu::BufferBindingType::Storage { read_only: true },
has_dynamic_offset: false,
min_binding_size: None,
},
count: None,
},
wgpu::BindGroupLayoutEntry {
binding: 1,
visibility: wgpu::ShaderStages::VERTEX,
ty: wgpu::BindingType::Buffer {
ty: wgpu::BufferBindingType::Storage { read_only: true },
has_dynamic_offset: false,
min_binding_size: None,
},
count: None,
},
],
});
Self {
bind_group_layout,
meshes: HashMap::new(),
}
}
pub(crate) fn pack(weights: &SkinWeights) -> Vec<PackedSkinVertex> {
weights
.joint_indices
.iter()
.zip(weights.joint_weights.iter())
.map(|(j, w)| {
let j0 = j[0] as u32;
let j1 = j[1] as u32;
let j2 = j[2] as u32;
let j3 = j[3] as u32;
PackedSkinVertex {
weights: *w,
joints_01: j0 | (j1 << 16),
joints_23: j2 | (j3 << 16),
_pad: [0, 0],
}
})
.collect()
}
}
impl ViewportGpuResources {
pub fn supports_gpu_skinning(&self) -> bool {
true
}
pub fn set_skin_weights(
&mut self,
device: &wgpu::Device,
mesh_id: MeshId,
weights: &SkinWeights,
) {
let packed = SkinningState::pack(weights);
let weights_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
label: Some("skin_weights_buffer"),
contents: bytemuck::cast_slice(&packed),
usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_DST,
});
self.skinning.meshes.insert(
mesh_id,
MeshSkinning {
weights_buffer,
vertex_count: packed.len() as u32,
instances: HashMap::new(),
},
);
}
pub fn set_skin_palette(
&mut self,
device: &wgpu::Device,
queue: &wgpu::Queue,
mesh_id: MeshId,
instance_id: u32,
palette: &[glam::Mat4],
) -> bool {
let bgl = &self.skinning.bind_group_layout;
let mesh = match self.skinning.meshes.get_mut(&mesh_id) {
Some(m) => m,
None => return false,
};
let joints_needed = palette.len() as u32;
let needs_realloc = match mesh.instances.get(&instance_id) {
Some(inst) => inst.joint_capacity < joints_needed,
None => true,
};
if needs_realloc {
let capacity = joints_needed.max(1);
let buffer = device.create_buffer(&wgpu::BufferDescriptor {
label: Some("skin_palette_buffer"),
size: (capacity as u64) * std::mem::size_of::<[[f32; 4]; 4]>() as u64,
usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_DST,
mapped_at_creation: false,
});
let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
label: Some("skin_bind_group"),
layout: bgl,
entries: &[
wgpu::BindGroupEntry {
binding: 0,
resource: mesh.weights_buffer.as_entire_binding(),
},
wgpu::BindGroupEntry {
binding: 1,
resource: buffer.as_entire_binding(),
},
],
});
mesh.instances.insert(
instance_id,
InstancePalette {
buffer,
bind_group,
joint_capacity: capacity,
},
);
}
let inst = mesh.instances.get(&instance_id).unwrap();
let bytes: Vec<[[f32; 4]; 4]> = palette.iter().map(|m| m.to_cols_array_2d()).collect();
queue.write_buffer(&inst.buffer, 0, bytemuck::cast_slice(&bytes));
true
}
pub fn is_skinned_mesh(&self, mesh_id: MeshId) -> bool {
self.skinning.meshes.contains_key(&mesh_id)
}
pub(crate) fn skin_instance_bind_group(
&self,
mesh_id: MeshId,
instance_id: u32,
) -> Option<&wgpu::BindGroup> {
self.skinning
.meshes
.get(&mesh_id)?
.instances
.get(&instance_id)
.map(|p| &p.bind_group)
}
}