use crate::context::Context;
use bytemuck::{Pod, Zeroable};
use std::cell::RefCell;
pub const MAX_MORPH_TARGETS: usize = 64;
#[repr(C)]
#[derive(Copy, Clone, Debug, Pod, Zeroable)]
pub struct DeformControl {
pub num_targets: u32,
pub num_vertices: u32,
pub has_skin: u32,
pub has_morph_normals: u32,
pub weights: [[f32; 4]; MAX_MORPH_TARGETS / 4],
}
impl Default for DeformControl {
fn default() -> Self {
Zeroable::zeroed()
}
}
impl DeformControl {
pub fn set_weights(&mut self, weights: &[f32]) {
let n = weights.len().min(MAX_MORPH_TARGETS);
self.num_targets = n as u32;
self.weights = [[0.0; 4]; MAX_MORPH_TARGETS / 4];
for (i, &w) in weights[..n].iter().enumerate() {
self.weights[i >> 2][i & 3] = w;
}
}
}
struct DeformGlobals {
layout: wgpu::BindGroupLayout,
identity_palette: wgpu::Buffer,
dummy_joints: wgpu::Buffer,
dummy_weights: wgpu::Buffer,
dummy_morph: wgpu::Buffer,
}
thread_local! {
static GLOBALS: RefCell<Option<DeformGlobals>> = const { RefCell::new(None) };
}
impl DeformGlobals {
fn new() -> Self {
let ctxt = Context::get();
let storage = |binding: u32| wgpu::BindGroupLayoutEntry {
binding,
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,
};
let layout = ctxt.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
label: Some("deform_bind_group_layout"),
entries: &[
storage(0),
storage(1),
storage(2),
storage(3),
storage(4),
wgpu::BindGroupLayoutEntry {
binding: 5,
visibility: wgpu::ShaderStages::VERTEX,
ty: wgpu::BindingType::Buffer {
ty: wgpu::BufferBindingType::Uniform,
has_dynamic_offset: false,
min_binding_size: None,
},
count: None,
},
],
});
let identity = glamx::Mat4::IDENTITY.to_cols_array();
let identity_palette = ctxt.create_buffer(&wgpu::BufferDescriptor {
label: Some("deform_identity_palette"),
size: std::mem::size_of::<[f32; 16]>() as u64,
usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_DST,
mapped_at_creation: false,
});
ctxt.write_buffer(&identity_palette, 0, bytemuck::cast_slice(&identity));
let dummy = |label, bytes: &[u8]| {
let buf = ctxt.create_buffer(&wgpu::BufferDescriptor {
label: Some(label),
size: bytes.len() as u64,
usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_DST,
mapped_at_creation: false,
});
ctxt.write_buffer(&buf, 0, bytes);
buf
};
DeformGlobals {
layout,
identity_palette,
dummy_joints: dummy("deform_dummy_joints", bytemuck::cast_slice(&[0u32; 4])),
dummy_weights: dummy("deform_dummy_weights", bytemuck::cast_slice(&[0.0f32; 4])),
dummy_morph: dummy("deform_dummy_morph", bytemuck::cast_slice(&[0.0f32; 4])),
}
}
}
fn with_globals<R>(f: impl FnOnce(&DeformGlobals) -> R) -> R {
GLOBALS.with(|cell| {
if cell.borrow().is_none() {
*cell.borrow_mut() = Some(DeformGlobals::new());
}
f(cell.borrow().as_ref().unwrap())
})
}
pub fn deform_bind_group_layout() -> wgpu::BindGroupLayout {
with_globals(|g| g.layout.clone())
}
pub struct DeformGpu {
control: wgpu::Buffer,
bind_group: Option<wgpu::BindGroup>,
key: Option<[usize; 5]>,
}
impl DeformGpu {
pub fn new() -> Self {
let ctxt = Context::get();
let control = ctxt.create_buffer(&wgpu::BufferDescriptor {
label: Some("deform_control_buffer"),
size: std::mem::size_of::<DeformControl>() as u64,
usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
mapped_at_creation: false,
});
Self {
control,
bind_group: None,
key: None,
}
}
pub fn update(
&mut self,
ctrl: &DeformControl,
palette: Option<&wgpu::Buffer>,
joints: Option<&wgpu::Buffer>,
weights: Option<&wgpu::Buffer>,
morph_pos: Option<&wgpu::Buffer>,
morph_nrm: Option<&wgpu::Buffer>,
) {
let ctxt = Context::get();
ctxt.write_buffer(&self.control, 0, bytemuck::bytes_of(ctrl));
let ptr = |b: Option<&wgpu::Buffer>| b.map_or(0, |x| x as *const wgpu::Buffer as usize);
let key = [
ptr(palette),
ptr(joints),
ptr(weights),
ptr(morph_pos),
ptr(morph_nrm),
];
if self.bind_group.is_none() || self.key != Some(key) {
self.bind_group = Some(build_deform_bind_group(
"deform_bind_group",
palette,
joints,
weights,
morph_pos,
morph_nrm,
&self.control,
));
self.key = Some(key);
}
}
pub fn bind_group(&self) -> Option<&wgpu::BindGroup> {
self.bind_group.as_ref()
}
}
impl Default for DeformGpu {
fn default() -> Self {
Self::new()
}
}
pub fn build_deform_bind_group(
label: &str,
palette: Option<&wgpu::Buffer>,
joints: Option<&wgpu::Buffer>,
weights: Option<&wgpu::Buffer>,
morph_pos: Option<&wgpu::Buffer>,
morph_nrm: Option<&wgpu::Buffer>,
control: &wgpu::Buffer,
) -> wgpu::BindGroup {
let ctxt = Context::get();
with_globals(|g| {
let palette = palette.unwrap_or(&g.identity_palette);
let joints = joints.unwrap_or(&g.dummy_joints);
let weights = weights.unwrap_or(&g.dummy_weights);
let morph_pos = morph_pos.unwrap_or(&g.dummy_morph);
let morph_nrm = morph_nrm.unwrap_or(&g.dummy_morph);
ctxt.create_bind_group(&wgpu::BindGroupDescriptor {
label: Some(label),
layout: &g.layout,
entries: &[
wgpu::BindGroupEntry {
binding: 0,
resource: palette.as_entire_binding(),
},
wgpu::BindGroupEntry {
binding: 1,
resource: joints.as_entire_binding(),
},
wgpu::BindGroupEntry {
binding: 2,
resource: weights.as_entire_binding(),
},
wgpu::BindGroupEntry {
binding: 3,
resource: morph_pos.as_entire_binding(),
},
wgpu::BindGroupEntry {
binding: 4,
resource: morph_nrm.as_entire_binding(),
},
wgpu::BindGroupEntry {
binding: 5,
resource: control.as_entire_binding(),
},
],
})
})
}