mirror of
https://github.com/bevyengine/bevy
synced 2025-02-16 14:08:32 +00:00
See #3165 and #3175 # Objective - @superdump was having trouble with this loop in the GLTF loader. ## Solution - Make it probably linear. - Measured times: - Old: 40s, new: 200ms I think there's still room for improvement. For example, I think making the nodes be in `Arc`s could be a significant gain, since currently there's duplication all the way down the tree. Co-authored-by: Carter Anderson <mcanders1@gmail.com>
This commit is contained in:
parent
0bf90bb98d
commit
f512c853d7
3 changed files with 95 additions and 67 deletions
|
@ -22,6 +22,7 @@ bevy_pbr2 = { path = "../bevy_pbr2", version = "0.5.0" }
|
||||||
bevy_reflect = { path = "../../crates/bevy_reflect", version = "0.5.0", features = ["bevy"] }
|
bevy_reflect = { path = "../../crates/bevy_reflect", version = "0.5.0", features = ["bevy"] }
|
||||||
bevy_render2 = { path = "../bevy_render2", version = "0.5.0" }
|
bevy_render2 = { path = "../bevy_render2", version = "0.5.0" }
|
||||||
bevy_transform = { path = "../../crates/bevy_transform", version = "0.5.0" }
|
bevy_transform = { path = "../../crates/bevy_transform", version = "0.5.0" }
|
||||||
|
bevy_utils = { path = "../../crates/bevy_utils", version = "0.5.0" }
|
||||||
bevy_math = { path = "../../crates/bevy_math", version = "0.5.0" }
|
bevy_math = { path = "../../crates/bevy_math", version = "0.5.0" }
|
||||||
bevy_scene = { path = "../../crates/bevy_scene", version = "0.5.0" }
|
bevy_scene = { path = "../../crates/bevy_scene", version = "0.5.0" }
|
||||||
bevy_log = { path = "../../crates/bevy_log", version = "0.5.0" }
|
bevy_log = { path = "../../crates/bevy_log", version = "0.5.0" }
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
use std::collections::HashMap;
|
use bevy_utils::HashMap;
|
||||||
|
|
||||||
mod loader;
|
mod loader;
|
||||||
pub use loader::*;
|
pub use loader::*;
|
||||||
|
|
|
@ -21,15 +21,13 @@ use bevy_transform::{
|
||||||
hierarchy::{BuildWorldChildren, WorldChildBuilder},
|
hierarchy::{BuildWorldChildren, WorldChildBuilder},
|
||||||
prelude::{GlobalTransform, Transform},
|
prelude::{GlobalTransform, Transform},
|
||||||
};
|
};
|
||||||
|
use bevy_utils::{HashMap, HashSet};
|
||||||
use gltf::{
|
use gltf::{
|
||||||
mesh::Mode,
|
mesh::Mode,
|
||||||
texture::{MagFilter, MinFilter, WrappingMode},
|
texture::{MagFilter, MinFilter, WrappingMode},
|
||||||
Material, Primitive,
|
Material, Primitive,
|
||||||
};
|
};
|
||||||
use std::{
|
use std::{collections::VecDeque, path::Path};
|
||||||
collections::{HashMap, HashSet},
|
|
||||||
path::Path,
|
|
||||||
};
|
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
use wgpu::{AddressMode, FilterMode, PrimitiveTopology, SamplerDescriptor, TextureFormat};
|
use wgpu::{AddressMode, FilterMode, PrimitiveTopology, SamplerDescriptor, TextureFormat};
|
||||||
|
|
||||||
|
@ -83,8 +81,8 @@ async fn load_gltf<'a, 'b>(
|
||||||
let buffer_data = load_buffers(&gltf, load_context, load_context.path()).await?;
|
let buffer_data = load_buffers(&gltf, load_context, load_context.path()).await?;
|
||||||
|
|
||||||
let mut materials = vec![];
|
let mut materials = vec![];
|
||||||
let mut named_materials = HashMap::new();
|
let mut named_materials = HashMap::default();
|
||||||
let mut linear_textures = HashSet::new();
|
let mut linear_textures = HashSet::default();
|
||||||
for material in gltf.materials() {
|
for material in gltf.materials() {
|
||||||
let handle = load_material(&material, load_context);
|
let handle = load_material(&material, load_context);
|
||||||
if let Some(name) = material.name() {
|
if let Some(name) = material.name() {
|
||||||
|
@ -106,7 +104,7 @@ async fn load_gltf<'a, 'b>(
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut meshes = vec![];
|
let mut meshes = vec![];
|
||||||
let mut named_meshes = HashMap::new();
|
let mut named_meshes = HashMap::default();
|
||||||
for mesh in gltf.meshes() {
|
for mesh in gltf.meshes() {
|
||||||
let mut primitives = vec![];
|
let mut primitives = vec![];
|
||||||
for primitive in mesh.primitives() {
|
for primitive in mesh.primitives() {
|
||||||
|
@ -195,7 +193,7 @@ async fn load_gltf<'a, 'b>(
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut nodes_intermediate = vec![];
|
let mut nodes_intermediate = vec![];
|
||||||
let mut named_nodes_intermediate = HashMap::new();
|
let mut named_nodes_intermediate = HashMap::default();
|
||||||
for node in gltf.nodes() {
|
for node in gltf.nodes() {
|
||||||
let node_label = node_label(&node);
|
let node_label = node_label(&node);
|
||||||
nodes_intermediate.push((
|
nodes_intermediate.push((
|
||||||
|
@ -229,7 +227,7 @@ async fn load_gltf<'a, 'b>(
|
||||||
named_nodes_intermediate.insert(name, node.index());
|
named_nodes_intermediate.insert(name, node.index());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let nodes = resolve_node_hierarchy(nodes_intermediate)
|
let nodes = resolve_node_hierarchy(nodes_intermediate, load_context.path())
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|(label, node)| load_context.set_labeled_asset(&label, LoadedAsset::new(node)))
|
.map(|(label, node)| load_context.set_labeled_asset(&label, LoadedAsset::new(node)))
|
||||||
.collect::<Vec<bevy_asset::Handle<GltfNode>>>();
|
.collect::<Vec<bevy_asset::Handle<GltfNode>>>();
|
||||||
|
@ -275,7 +273,7 @@ async fn load_gltf<'a, 'b>(
|
||||||
});
|
});
|
||||||
|
|
||||||
let mut scenes = vec![];
|
let mut scenes = vec![];
|
||||||
let mut named_scenes = HashMap::new();
|
let mut named_scenes = HashMap::default();
|
||||||
for scene in gltf.scenes() {
|
for scene in gltf.scenes() {
|
||||||
let mut err = None;
|
let mut err = None;
|
||||||
let mut world = World::default();
|
let mut world = World::default();
|
||||||
|
@ -716,42 +714,51 @@ async fn load_buffers(
|
||||||
|
|
||||||
fn resolve_node_hierarchy(
|
fn resolve_node_hierarchy(
|
||||||
nodes_intermediate: Vec<(String, GltfNode, Vec<usize>)>,
|
nodes_intermediate: Vec<(String, GltfNode, Vec<usize>)>,
|
||||||
|
asset_path: &Path,
|
||||||
) -> Vec<(String, GltfNode)> {
|
) -> Vec<(String, GltfNode)> {
|
||||||
let mut max_steps = nodes_intermediate.len();
|
let mut has_errored = false;
|
||||||
let mut nodes_step = nodes_intermediate
|
let mut empty_children = VecDeque::new();
|
||||||
|
let mut parents = vec![None; nodes_intermediate.len()];
|
||||||
|
let mut unprocessed_nodes = nodes_intermediate
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.enumerate()
|
.enumerate()
|
||||||
.map(|(i, (label, node, children))| (i, label, node, children))
|
.map(|(i, (label, node, children))| {
|
||||||
.collect::<Vec<_>>();
|
for child in children.iter() {
|
||||||
let mut nodes = std::collections::HashMap::<usize, (String, GltfNode)>::new();
|
if let Some(parent) = parents.get_mut(*child) {
|
||||||
while max_steps > 0 && !nodes_step.is_empty() {
|
*parent = Some(i);
|
||||||
if let Some((index, label, node, _)) = nodes_step
|
} else if !has_errored {
|
||||||
.iter()
|
has_errored = true;
|
||||||
.find(|(_, _, _, children)| children.is_empty())
|
warn!("Unexpected child in GLTF Mesh {}", child);
|
||||||
.cloned()
|
|
||||||
{
|
|
||||||
nodes.insert(index, (label, node));
|
|
||||||
for (_, _, node, children) in nodes_step.iter_mut() {
|
|
||||||
if let Some((i, _)) = children
|
|
||||||
.iter()
|
|
||||||
.enumerate()
|
|
||||||
.find(|(_, child_index)| **child_index == index)
|
|
||||||
{
|
|
||||||
children.remove(i);
|
|
||||||
|
|
||||||
if let Some((_, child_node)) = nodes.get(&index) {
|
|
||||||
node.children.push(child_node.clone())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
nodes_step = nodes_step
|
let children = children.into_iter().collect::<HashSet<_>>();
|
||||||
.into_iter()
|
if children.is_empty() {
|
||||||
.filter(|(i, _, _, _)| *i != index)
|
empty_children.push_back(i);
|
||||||
.collect()
|
}
|
||||||
}
|
(i, (label, node, children))
|
||||||
max_steps -= 1;
|
})
|
||||||
}
|
.collect::<HashMap<_, _>>();
|
||||||
|
let mut nodes = std::collections::HashMap::<usize, (String, GltfNode)>::new();
|
||||||
|
while let Some(index) = empty_children.pop_front() {
|
||||||
|
let (label, node, children) = unprocessed_nodes.remove(&index).unwrap();
|
||||||
|
assert!(children.is_empty());
|
||||||
|
nodes.insert(index, (label, node));
|
||||||
|
if let Some(parent_index) = parents[index] {
|
||||||
|
let (_, parent_node, parent_children) =
|
||||||
|
unprocessed_nodes.get_mut(&parent_index).unwrap();
|
||||||
|
|
||||||
|
assert!(parent_children.remove(&index));
|
||||||
|
if let Some((_, child_node)) = nodes.get(&index) {
|
||||||
|
parent_node.children.push(child_node.clone())
|
||||||
|
}
|
||||||
|
if parent_children.is_empty() {
|
||||||
|
empty_children.push_back(parent_index);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !unprocessed_nodes.is_empty() {
|
||||||
|
warn!("GLTF model must be a tree: {:?}", asset_path);
|
||||||
|
}
|
||||||
let mut nodes_to_sort = nodes.into_iter().collect::<Vec<_>>();
|
let mut nodes_to_sort = nodes.into_iter().collect::<Vec<_>>();
|
||||||
nodes_to_sort.sort_by_key(|(i, _)| *i);
|
nodes_to_sort.sort_by_key(|(i, _)| *i);
|
||||||
nodes_to_sort
|
nodes_to_sort
|
||||||
|
@ -799,6 +806,8 @@ impl<'a> DataUri<'a> {
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
|
use std::path::PathBuf;
|
||||||
|
|
||||||
use super::resolve_node_hierarchy;
|
use super::resolve_node_hierarchy;
|
||||||
use crate::GltfNode;
|
use crate::GltfNode;
|
||||||
|
|
||||||
|
@ -813,7 +822,10 @@ mod test {
|
||||||
}
|
}
|
||||||
#[test]
|
#[test]
|
||||||
fn node_hierarchy_single_node() {
|
fn node_hierarchy_single_node() {
|
||||||
let result = resolve_node_hierarchy(vec![("l1".to_string(), GltfNode::empty(), vec![])]);
|
let result = resolve_node_hierarchy(
|
||||||
|
vec![("l1".to_string(), GltfNode::empty(), vec![])],
|
||||||
|
PathBuf::new().as_path(),
|
||||||
|
);
|
||||||
|
|
||||||
assert_eq!(result.len(), 1);
|
assert_eq!(result.len(), 1);
|
||||||
assert_eq!(result[0].0, "l1");
|
assert_eq!(result[0].0, "l1");
|
||||||
|
@ -822,10 +834,13 @@ mod test {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn node_hierarchy_no_hierarchy() {
|
fn node_hierarchy_no_hierarchy() {
|
||||||
let result = resolve_node_hierarchy(vec![
|
let result = resolve_node_hierarchy(
|
||||||
("l1".to_string(), GltfNode::empty(), vec![]),
|
vec![
|
||||||
("l2".to_string(), GltfNode::empty(), vec![]),
|
("l1".to_string(), GltfNode::empty(), vec![]),
|
||||||
]);
|
("l2".to_string(), GltfNode::empty(), vec![]),
|
||||||
|
],
|
||||||
|
PathBuf::new().as_path(),
|
||||||
|
);
|
||||||
|
|
||||||
assert_eq!(result.len(), 2);
|
assert_eq!(result.len(), 2);
|
||||||
assert_eq!(result[0].0, "l1");
|
assert_eq!(result[0].0, "l1");
|
||||||
|
@ -836,10 +851,13 @@ mod test {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn node_hierarchy_simple_hierarchy() {
|
fn node_hierarchy_simple_hierarchy() {
|
||||||
let result = resolve_node_hierarchy(vec![
|
let result = resolve_node_hierarchy(
|
||||||
("l1".to_string(), GltfNode::empty(), vec![1]),
|
vec![
|
||||||
("l2".to_string(), GltfNode::empty(), vec![]),
|
("l1".to_string(), GltfNode::empty(), vec![1]),
|
||||||
]);
|
("l2".to_string(), GltfNode::empty(), vec![]),
|
||||||
|
],
|
||||||
|
PathBuf::new().as_path(),
|
||||||
|
);
|
||||||
|
|
||||||
assert_eq!(result.len(), 2);
|
assert_eq!(result.len(), 2);
|
||||||
assert_eq!(result[0].0, "l1");
|
assert_eq!(result[0].0, "l1");
|
||||||
|
@ -850,15 +868,18 @@ mod test {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn node_hierarchy_hierarchy() {
|
fn node_hierarchy_hierarchy() {
|
||||||
let result = resolve_node_hierarchy(vec![
|
let result = resolve_node_hierarchy(
|
||||||
("l1".to_string(), GltfNode::empty(), vec![1]),
|
vec![
|
||||||
("l2".to_string(), GltfNode::empty(), vec![2]),
|
("l1".to_string(), GltfNode::empty(), vec![1]),
|
||||||
("l3".to_string(), GltfNode::empty(), vec![3, 4, 5]),
|
("l2".to_string(), GltfNode::empty(), vec![2]),
|
||||||
("l4".to_string(), GltfNode::empty(), vec![6]),
|
("l3".to_string(), GltfNode::empty(), vec![3, 4, 5]),
|
||||||
("l5".to_string(), GltfNode::empty(), vec![]),
|
("l4".to_string(), GltfNode::empty(), vec![6]),
|
||||||
("l6".to_string(), GltfNode::empty(), vec![]),
|
("l5".to_string(), GltfNode::empty(), vec![]),
|
||||||
("l7".to_string(), GltfNode::empty(), vec![]),
|
("l6".to_string(), GltfNode::empty(), vec![]),
|
||||||
]);
|
("l7".to_string(), GltfNode::empty(), vec![]),
|
||||||
|
],
|
||||||
|
PathBuf::new().as_path(),
|
||||||
|
);
|
||||||
|
|
||||||
assert_eq!(result.len(), 7);
|
assert_eq!(result.len(), 7);
|
||||||
assert_eq!(result[0].0, "l1");
|
assert_eq!(result[0].0, "l1");
|
||||||
|
@ -879,20 +900,26 @@ mod test {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn node_hierarchy_cyclic() {
|
fn node_hierarchy_cyclic() {
|
||||||
let result = resolve_node_hierarchy(vec![
|
let result = resolve_node_hierarchy(
|
||||||
("l1".to_string(), GltfNode::empty(), vec![1]),
|
vec![
|
||||||
("l2".to_string(), GltfNode::empty(), vec![0]),
|
("l1".to_string(), GltfNode::empty(), vec![1]),
|
||||||
]);
|
("l2".to_string(), GltfNode::empty(), vec![0]),
|
||||||
|
],
|
||||||
|
PathBuf::new().as_path(),
|
||||||
|
);
|
||||||
|
|
||||||
assert_eq!(result.len(), 0);
|
assert_eq!(result.len(), 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn node_hierarchy_missing_node() {
|
fn node_hierarchy_missing_node() {
|
||||||
let result = resolve_node_hierarchy(vec![
|
let result = resolve_node_hierarchy(
|
||||||
("l1".to_string(), GltfNode::empty(), vec![2]),
|
vec![
|
||||||
("l2".to_string(), GltfNode::empty(), vec![]),
|
("l1".to_string(), GltfNode::empty(), vec![2]),
|
||||||
]);
|
("l2".to_string(), GltfNode::empty(), vec![]),
|
||||||
|
],
|
||||||
|
PathBuf::new().as_path(),
|
||||||
|
);
|
||||||
|
|
||||||
assert_eq!(result.len(), 1);
|
assert_eq!(result.len(), 1);
|
||||||
assert_eq!(result[0].0, "l2");
|
assert_eq!(result[0].0, "l2");
|
||||||
|
|
Loading…
Add table
Reference in a new issue