From ae33d9a919c2ff05775a56fdff81d4bb9b2d14bd Mon Sep 17 00:00:00 2001 From: scurest Date: Tue, 21 Jan 2020 19:57:19 -0600 Subject: [PATCH] Refactor: pack all weights in a skeleton into one buffer Instead of every vert having a SmallVec of its influences, have it store an offset into the packed buffer of all the influences instead. This is way more efficient. Like, 10% of the size it was before. Also lets us drop the smallvec dependency. --- Cargo.lock | 1 - Cargo.toml | 1 - src/convert/collada/mod.rs | 20 ++++++------ src/convert/gltf/mod.rs | 26 +++++++-------- src/main.rs | 1 - src/skeleton/joint_tree.rs | 65 ++++++++++++++++++++++---------------- src/skeleton/mod.rs | 28 ++++++++++------ 7 files changed, 77 insertions(+), 65 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 14be8d0..331c8e3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -42,7 +42,6 @@ dependencies = [ "json 0.11.13 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", "png 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)", - "smallvec 0.6.7 (registry+https://github.com/rust-lang/crates.io-index)", "termcolor 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", "time 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)", "wild 2.0.2 (registry+https://github.com/rust-lang/crates.io-index)", diff --git a/Cargo.toml b/Cargo.toml index 8ca6592..5bc1538 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,7 +13,6 @@ glium = "0.23.0" json = "0.11" log = { version = "0.4.6", features = ["std"] } png = "0.11.0" -smallvec = "0.6.5" termcolor = "0.3.5" time = "0.1.36" wild = "2.0.2" diff --git a/src/convert/collada/mod.rs b/src/convert/collada/mod.rs index 8bb3be8..8401f8a 100644 --- a/src/convert/collada/mod.rs +++ b/src/convert/collada/mod.rs @@ -411,10 +411,8 @@ fn library_controllers(xml: &mut Xml, ctx: &Ctx) { let encode = |x: f32| (x * 4096.0) as u32; let decode = |x: u32| x as f64 / 4096.0; weights_lut.clear(); - for v in &ctx.skel.vertices { - for influence in &v.influences { - weights_lut.push(encode(influence.weight)); - } + for w in &ctx.skel.weights { + weights_lut.push(encode(w.weight)); } // Here is the list of all weights. xml!(xml; @@ -439,21 +437,21 @@ fn library_controllers(xml: &mut Xml, ctx: &Ctx) { /joints>; ); - let num_verts = ctx.skel.vertices.len(); + let num_verts = ctx.prims.vertices.len(); xml!(xml; ; ; ; - for v in (&ctx.skel.vertices) { - (v.influences.len())" " + for vi in (0 .. ctx.prims.vertices.len()) { + (ctx.skel.vert_weights(vi).len())" " } ; - for v in (&ctx.skel.vertices) { - for influence in (&v.influences) { - (influence.joint)" " - (weights_lut.idx(&encode(influence.weight)))" " + for vi in (0 .. ctx.prims.vertices.len()) { + for w in (ctx.skel.vert_weights(vi)) { + (w.joint)" " + (weights_lut.idx(&encode(w.weight)))" " } } ; diff --git a/src/convert/gltf/mod.rs b/src/convert/gltf/mod.rs index 96ae0e0..cce6a03 100644 --- a/src/convert/gltf/mod.rs +++ b/src/convert/gltf/mod.rs @@ -210,7 +210,7 @@ fn mesh(ctx: &Ctx, gltf: &mut GlTF) { // glTF gives joint/weight influences in sets of 4 (JOINT_0 is a VEC4 // accessor with the first four joints, JOINTS_1 has the next four, etc). // Find out how many sets we need. - let num_sets = (ctx.skel.max_num_influences + 3) / 4; + let num_sets = ((ctx.skel.max_num_weights + 3) / 4) as usize; // Make sure joints fit in a byte assert!(ctx.skel.tree.node_count() <= 255); @@ -223,15 +223,14 @@ fn mesh(ctx: &Ctx, gltf: &mut GlTF) { }); let dat_len = { let dat = &mut gltf.buffers[buf].bytes; - for sv in &ctx.skel.vertices { - let mut i = 0; - while i != 4 * num_sets { - if i < sv.influences.len() { - dat.push(sv.influences[i].joint as u8); + for vi in 0 .. verts.len() { + let ws = ctx.skel.vert_weights(vi); + for i in 0 .. 4 * num_sets { + if i < ws.len() { + dat.push(ws[i].joint as u8); } else { dat.push(0); } - i += 1; } } dat.len() @@ -260,15 +259,14 @@ fn mesh(ctx: &Ctx, gltf: &mut GlTF) { }); let dat_len = { let dat = &mut gltf.buffers[buf].bytes; - for sv in &ctx.skel.vertices { - let mut i = 0; - while i != 4 * num_sets { - if i < sv.influences.len() { - dat.push_normalized_u8(sv.influences[i].weight); + for vi in 0 .. verts.len() { + let ws = ctx.skel.vert_weights(vi); + for i in 0 .. 4 * num_sets { + if i < ws.len() { + dat.push_normalized_u8(ws[i].weight); } else { - dat.push_normalized_u8(0.0); + dat.push(0); } - i += 1; } } dat.len() diff --git a/src/main.rs b/src/main.rs index 550d17f..44ee974 100644 --- a/src/main.rs +++ b/src/main.rs @@ -13,7 +13,6 @@ extern crate time; extern crate png; extern crate termcolor; extern crate atty; -extern crate smallvec; #[macro_use] extern crate json; extern crate wild; diff --git a/src/skeleton/joint_tree.rs b/src/skeleton/joint_tree.rs index 8b15af9..24726a7 100644 --- a/src/skeleton/joint_tree.rs +++ b/src/skeleton/joint_tree.rs @@ -113,27 +113,38 @@ use cgmath::{Matrix4, SquareMatrix, One, ApproxEq}; use super::vertex_record::VertexRecord; use super::{SMatrix, AMatrix}; -use super::{Skeleton, Joint, Transform, SkinVertex, Influence}; +use super::{Skeleton, Joint, Transform, Weight, WeightsOfs}; use nitro::Model; use util::tree::{Tree, NodeIdx}; pub fn build_skeleton(vr: &VertexRecord, model: &Model, objects: &[Matrix4]) -> Skeleton { let mut b = Builder::new(model, objects); - // Caches the right skinvertex for each of the matrices in vr. - let mut skin_vert_cache: Vec> = vec![None; vr.matrices.len()]; - let mut max_num_influences = 0; + // Caches the WeightOfs for each of the matrices in vr. + let mut mat_cache: Vec> = vec![None; vr.matrices.len()]; + let mut max_num_weights = 0; + let mut weights: Vec = Vec::with_capacity(vr.matrices.len()); - let vertices = vr.vertices.iter().map(|&mat_idx| { - if skin_vert_cache[mat_idx as usize].is_none() { - let mut sv = b.amatrix_to_skinvert(&vr.matrices[mat_idx as usize]); - simplify_skinvert(&mut sv); - max_num_influences = max_num_influences.max(sv.influences.len()); - skin_vert_cache[mat_idx as usize] = Some(sv); + let verts = vr.vertices.iter().map(|&mat_idx| { + if mat_cache[mat_idx as usize].is_none() { + let mut ws = b.amatrix_to_weights(&vr.matrices[mat_idx as usize]); + simplify_weights(&mut ws); + assert!(ws.len() <= 255); + max_num_weights = max_num_weights.max(ws.len() as u8); + + // Add the weights for this vert to the weights array + // and create an offset to them. + let start = weights.len(); + let len = ws.len(); + assert!(start <= 0xffffff); + weights.append(&mut ws); + let ofs = WeightsOfs(start as u32 | ((len as u32) << 24)); + + mat_cache[mat_idx as usize] = Some(ofs); } - skin_vert_cache[mat_idx as usize].as_ref().unwrap().clone() - }).collect(); + mat_cache[mat_idx as usize].unwrap() + }).collect::>(); if b.unusual_matrices { warn!("unusual matrices encountered in model {}; the skin for this \ @@ -160,8 +171,9 @@ pub fn build_skeleton(vr: &VertexRecord, model: &Model, objects: &[Matrix4] Skeleton { tree: b.graph, root, - vertices, - max_num_influences, + max_num_weights, + weights, + verts, } } @@ -216,15 +228,14 @@ impl<'a, 'b> Builder<'a, 'b> { self.roots.push(root); } - fn amatrix_to_skinvert(&mut self, amatrix: &AMatrix) -> SkinVertex { + fn amatrix_to_weights(&mut self, amatrix: &AMatrix) -> Vec { self.detect_unusual_matrices(amatrix); - let influences = amatrix.terms.iter().map(|term| { + amatrix.terms.iter().map(|term| { let weight = term.weight; let joint = self.cmatrix_to_joint(&term.cmat.factors); - Influence { weight, joint } - }).collect(); - SkinVertex { influences } + Weight { weight, joint } + }).collect() } fn cmatrix_to_joint(&mut self, mut factors: &[SMatrix]) -> NodeIdx { @@ -348,20 +359,20 @@ impl<'a, 'b> Builder<'a, 'b> { } } -fn simplify_skinvert(sv: &mut SkinVertex) { +fn simplify_weights(weights: &mut Vec) { // Group like terms. - for i in 0..sv.influences.len() { - for j in (i+1)..sv.influences.len() { - if sv.influences[i].joint == sv.influences[j].joint { - sv.influences[i].weight += sv.influences[j].weight; - sv.influences[j].weight = 0.0; + for i in 0..weights.len() { + for j in (i+1)..weights.len() { + if weights[i].joint == weights[j].joint { + weights[i].weight += weights[j].weight; + weights[j].weight = 0.0; } } } - sv.influences.retain(|influence| influence.weight != 0.0); + weights.retain(|influence| influence.weight != 0.0); // Sort by weight, highest to lowest - sv.influences.sort_by(|in1, in2| in2.weight.partial_cmp(&in1.weight).unwrap()); + weights.sort_by(|w1, w2| w2.weight.partial_cmp(&w1.weight).unwrap()); } /// Inverts a matrix, bumping its entries slightly if necessary until it is diff --git a/src/skeleton/mod.rs b/src/skeleton/mod.rs index bb3bfa9..470ddfa 100644 --- a/src/skeleton/mod.rs +++ b/src/skeleton/mod.rs @@ -57,16 +57,16 @@ pub use self::symbolic_matrix::{SMatrix, CMatrix, AMatrix, ATerm}; use cgmath::Matrix4; use nitro::Model; -use smallvec::SmallVec; use util::tree::{Tree, NodeIdx}; /// Skeleton (or skin) for a model. pub struct Skeleton { pub tree: Tree, pub root: NodeIdx, - pub vertices: Vec, - /// Largest number of influences on any vertex. - pub max_num_influences: usize, + pub max_num_weights: u8, // max weights on any vertex + + pub weights: Vec, // weights for all verts packed together + verts: Vec, // verts[vi] points to the weights for vertex vi in weights } pub struct Joint { @@ -85,18 +85,26 @@ pub enum Transform { Root, } -#[derive(Clone)] -pub struct SkinVertex { - pub influences: SmallVec<[Influence; 1]>, -} - #[derive(Copy, Clone)] -pub struct Influence { +pub struct Weight { pub weight: f32, pub joint: NodeIdx, } +/// Points to a slice skel.weights[start..start+len]. +/// start is the low 24 bits, len is the high 8 bits. +#[derive(Copy, Clone)] +struct WeightsOfs(u32); + + impl Skeleton { + pub fn vert_weights(&self, vi: usize) -> &[Weight] { + let ofs = self.verts[vi]; + let start = (ofs.0 & 0xffffff) as usize; + let len = (ofs.0 >> 24) as usize; + &self.weights[start .. start + len] + } + pub fn build(model: &Model, objects: &[Matrix4]) -> Skeleton { // First play back rendering commands recording the symbolic value of // the matrix applied to every vertex.