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.
This commit is contained in:
scurest 2020-01-21 19:57:19 -06:00
parent c81d9d5269
commit ae33d9a919
7 changed files with 77 additions and 65 deletions

1
Cargo.lock generated
View File

@ -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)",

View File

@ -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"

View File

@ -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;
<vertex_weights count=[(num_verts)]>;
<input semantic=["JOINT"] source=["#controller-joints"] offset=["0"]/>;
<input semantic=["WEIGHT"] source=["#controller-weights"] offset=["1"]/>;
<vcount>
for v in (&ctx.skel.vertices) {
(v.influences.len())" "
for vi in (0 .. ctx.prims.vertices.len()) {
(ctx.skel.vert_weights(vi).len())" "
}
</vcount>;
<v>
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)))" "
}
}
</v>;

View File

@ -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()

View File

@ -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;

View File

@ -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<f64>]) -> 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<Option<SkinVertex>> = 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<Option<WeightsOfs>> = vec![None; vr.matrices.len()];
let mut max_num_weights = 0;
let mut weights: Vec<Weight> = 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::<Vec<WeightsOfs>>();
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<f64>]
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<Weight> {
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<Weight>) {
// 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

View File

@ -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<Joint>,
pub root: NodeIdx,
pub vertices: Vec<SkinVertex>,
/// 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<Weight>, // weights for all verts packed together
verts: Vec<WeightsOfs>, // 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<f64>]) -> Skeleton {
// First play back rendering commands recording the symbolic value of
// the matrix applied to every vertex.