Merge ecf6d0613a
into f964ee1e3a
This commit is contained in:
commit
255995ea09
11
Cargo.toml
11
Cargo.toml
@ -3134,6 +3134,17 @@ description = "Loads an animated fox model and spawns lots of them. Good for tes
|
|||||||
category = "Stress Tests"
|
category = "Stress Tests"
|
||||||
wasm = true
|
wasm = true
|
||||||
|
|
||||||
|
[[example]]
|
||||||
|
name = "many_morph_targets"
|
||||||
|
path = "examples/stress_tests/many_morph_targets.rs"
|
||||||
|
doc-scrape-examples = true
|
||||||
|
|
||||||
|
[package.metadata.example.many_morph_targets]
|
||||||
|
name = "Many Morph Targets"
|
||||||
|
description = "Simple benchmark to test rendering many meshes with animated morph targets."
|
||||||
|
category = "Stress Tests"
|
||||||
|
wasm = true
|
||||||
|
|
||||||
[[example]]
|
[[example]]
|
||||||
name = "many_glyphs"
|
name = "many_glyphs"
|
||||||
path = "examples/stress_tests/many_glyphs.rs"
|
path = "examples/stress_tests/many_glyphs.rs"
|
||||||
|
@ -512,6 +512,7 @@ Example | Description
|
|||||||
[Many Glyphs](../examples/stress_tests/many_glyphs.rs) | Simple benchmark to test text rendering.
|
[Many Glyphs](../examples/stress_tests/many_glyphs.rs) | Simple benchmark to test text rendering.
|
||||||
[Many Gradients](../examples/stress_tests/many_gradients.rs) | Stress test for gradient rendering performance
|
[Many Gradients](../examples/stress_tests/many_gradients.rs) | Stress test for gradient rendering performance
|
||||||
[Many Lights](../examples/stress_tests/many_lights.rs) | Simple benchmark to test rendering many point lights. Run with `WGPU_SETTINGS_PRIO=webgl2` to restrict to uniform buffers and max 256 lights
|
[Many Lights](../examples/stress_tests/many_lights.rs) | Simple benchmark to test rendering many point lights. Run with `WGPU_SETTINGS_PRIO=webgl2` to restrict to uniform buffers and max 256 lights
|
||||||
|
[Many Morph Targets](../examples/stress_tests/many_morph_targets.rs) | Simple benchmark to test rendering many meshes with animated morph targets.
|
||||||
[Many Sprites](../examples/stress_tests/many_sprites.rs) | Displays many sprites in a grid arrangement! Used for performance testing. Use `--colored` to enable color tinted sprites.
|
[Many Sprites](../examples/stress_tests/many_sprites.rs) | Displays many sprites in a grid arrangement! Used for performance testing. Use `--colored` to enable color tinted sprites.
|
||||||
[Many Text2d](../examples/stress_tests/many_text2d.rs) | Displays many Text2d! Used for performance testing.
|
[Many Text2d](../examples/stress_tests/many_text2d.rs) | Displays many Text2d! Used for performance testing.
|
||||||
[Text Pipeline](../examples/stress_tests/text_pipeline.rs) | Text Pipeline benchmark
|
[Text Pipeline](../examples/stress_tests/text_pipeline.rs) | Text Pipeline benchmark
|
||||||
|
253
examples/stress_tests/many_morph_targets.rs
Normal file
253
examples/stress_tests/many_morph_targets.rs
Normal file
@ -0,0 +1,253 @@
|
|||||||
|
//! Simple benchmark to test rendering many meshes with animated morph targets.
|
||||||
|
|
||||||
|
use argh::FromArgs;
|
||||||
|
use bevy::{
|
||||||
|
diagnostic::{FrameTimeDiagnosticsPlugin, LogDiagnosticsPlugin},
|
||||||
|
prelude::*,
|
||||||
|
scene::SceneInstanceReady,
|
||||||
|
window::{PresentMode, WindowResolution},
|
||||||
|
winit::{UpdateMode, WinitSettings},
|
||||||
|
};
|
||||||
|
use core::{f32::consts::PI, str::FromStr};
|
||||||
|
use rand::{Rng, SeedableRng};
|
||||||
|
use rand_chacha::ChaCha8Rng;
|
||||||
|
|
||||||
|
/// Controls the weight values.
|
||||||
|
#[derive(PartialEq)]
|
||||||
|
enum ArgWeights {
|
||||||
|
/// Weights will change over time and be a mix of zero and non-zero.
|
||||||
|
Animated,
|
||||||
|
|
||||||
|
/// Set all the weights to one.
|
||||||
|
One,
|
||||||
|
|
||||||
|
/// Set all the weights to zero, minimizing vertex shader cost.
|
||||||
|
Zero,
|
||||||
|
|
||||||
|
/// Set all the weights to a very small value, so the pixel shader cost
|
||||||
|
/// should be similar to `Zero` but vertex shader cost the same as `One`.
|
||||||
|
Tiny,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FromStr for ArgWeights {
|
||||||
|
type Err = String;
|
||||||
|
|
||||||
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||||
|
match s {
|
||||||
|
"animated" => Ok(Self::Animated),
|
||||||
|
"zero" => Ok(Self::Zero),
|
||||||
|
"one" => Ok(Self::One),
|
||||||
|
"tiny" => Ok(Self::Tiny),
|
||||||
|
_ => Err("must be 'animated', 'one', `zero`, or 'tiny'".into()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Controls the camera.
|
||||||
|
#[derive(PartialEq)]
|
||||||
|
enum ArgCamera {
|
||||||
|
/// Keep all the meshes in view and at a reasonable size.
|
||||||
|
Default,
|
||||||
|
|
||||||
|
/// Zoom far out. This is used to reduce pixel shader costs and so emphasize
|
||||||
|
/// vertex shader costs.
|
||||||
|
Far,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FromStr for ArgCamera {
|
||||||
|
type Err = String;
|
||||||
|
|
||||||
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||||
|
match s {
|
||||||
|
"default" => Ok(Self::Default),
|
||||||
|
"far" => Ok(Self::Far),
|
||||||
|
_ => Err("must be 'default' or 'far'".into()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// `many_morph_targets` stress test
|
||||||
|
#[derive(FromArgs, Resource)]
|
||||||
|
struct Args {
|
||||||
|
/// number of meshes
|
||||||
|
#[argh(option, default = "1024")]
|
||||||
|
count: usize,
|
||||||
|
|
||||||
|
/// options: 'animated', 'one', 'zero', 'tiny'
|
||||||
|
#[argh(option, default = "ArgWeights::Animated")]
|
||||||
|
weights: ArgWeights,
|
||||||
|
|
||||||
|
/// options: 'default', 'far'
|
||||||
|
#[argh(option, default = "ArgCamera::Default")]
|
||||||
|
camera: ArgCamera,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
// `from_env` panics on the web
|
||||||
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
|
let args: Args = argh::from_env();
|
||||||
|
#[cfg(target_arch = "wasm32")]
|
||||||
|
let args = Args::from_args(&[], &[]).unwrap();
|
||||||
|
|
||||||
|
App::new()
|
||||||
|
.add_plugins((
|
||||||
|
DefaultPlugins.set(WindowPlugin {
|
||||||
|
primary_window: Some(Window {
|
||||||
|
title: "Many Morph Targets".to_string(),
|
||||||
|
present_mode: PresentMode::AutoNoVsync,
|
||||||
|
resolution: WindowResolution::new(1920.0, 1080.0)
|
||||||
|
.with_scale_factor_override(1.0),
|
||||||
|
..default()
|
||||||
|
}),
|
||||||
|
..Default::default()
|
||||||
|
}),
|
||||||
|
FrameTimeDiagnosticsPlugin::default(),
|
||||||
|
LogDiagnosticsPlugin::default(),
|
||||||
|
))
|
||||||
|
.insert_resource(WinitSettings {
|
||||||
|
focused_mode: UpdateMode::Continuous,
|
||||||
|
unfocused_mode: UpdateMode::Continuous,
|
||||||
|
})
|
||||||
|
.insert_resource(AmbientLight {
|
||||||
|
brightness: 1000.0,
|
||||||
|
..Default::default()
|
||||||
|
})
|
||||||
|
.insert_resource(args)
|
||||||
|
.add_systems(Startup, setup)
|
||||||
|
.run();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Component, Clone)]
|
||||||
|
struct AnimationToPlay {
|
||||||
|
graph_handle: Handle<AnimationGraph>,
|
||||||
|
index: AnimationNodeIndex,
|
||||||
|
speed: f32,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AnimationToPlay {
|
||||||
|
fn with_speed(&self, speed: f32) -> Self {
|
||||||
|
AnimationToPlay {
|
||||||
|
speed,
|
||||||
|
..self.clone()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn setup(
|
||||||
|
args: Res<Args>,
|
||||||
|
asset_server: Res<AssetServer>,
|
||||||
|
mut graphs: ResMut<Assets<AnimationGraph>>,
|
||||||
|
mut commands: Commands,
|
||||||
|
) {
|
||||||
|
const ASSET_PATH: &str = "models/animated/MorphStressTest.gltf";
|
||||||
|
|
||||||
|
let scene = SceneRoot(asset_server.load(GltfAssetLabel::Scene(0).from_asset(ASSET_PATH)));
|
||||||
|
|
||||||
|
let mut rng = ChaCha8Rng::seed_from_u64(856673);
|
||||||
|
|
||||||
|
let animations = (0..3)
|
||||||
|
.map(|gltf_index| {
|
||||||
|
let (graph, index) = AnimationGraph::from_clip(
|
||||||
|
asset_server.load(GltfAssetLabel::Animation(gltf_index).from_asset(ASSET_PATH)),
|
||||||
|
);
|
||||||
|
AnimationToPlay {
|
||||||
|
graph_handle: graphs.add(graph),
|
||||||
|
index,
|
||||||
|
speed: 1.0,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
|
// Arrange the meshes in a grid.
|
||||||
|
|
||||||
|
let count = args.count;
|
||||||
|
let x_dim = ((count as f32).sqrt().ceil() as usize).max(1);
|
||||||
|
let y_dim = count.div_ceil(x_dim);
|
||||||
|
|
||||||
|
for mesh_index in 0..count {
|
||||||
|
let animation = animations[mesh_index.rem_euclid(animations.len())].clone();
|
||||||
|
|
||||||
|
let x = 2.5 + (5.0 * ((mesh_index.rem_euclid(x_dim) as f32) - ((x_dim as f32) * 0.5)));
|
||||||
|
let y = -2.2 - (3.0 * ((mesh_index.div_euclid(x_dim) as f32) - ((y_dim as f32) * 0.5)));
|
||||||
|
|
||||||
|
// Randomly vary the animation speed so that the number of morph targets
|
||||||
|
// active on each frame is more likely to be stable.
|
||||||
|
|
||||||
|
let animation_speed = rng.r#gen::<f32>() + 0.5;
|
||||||
|
|
||||||
|
commands
|
||||||
|
.spawn((
|
||||||
|
animation.with_speed(animation_speed),
|
||||||
|
scene.clone(),
|
||||||
|
Transform::from_xyz(x, y, 0.0),
|
||||||
|
))
|
||||||
|
.observe(play_animation)
|
||||||
|
.observe(set_weights);
|
||||||
|
}
|
||||||
|
|
||||||
|
commands.spawn((
|
||||||
|
DirectionalLight::default(),
|
||||||
|
Transform::from_rotation(Quat::from_rotation_z(PI / 2.0)),
|
||||||
|
));
|
||||||
|
|
||||||
|
let camera_distance = (x_dim as f32)
|
||||||
|
* match args.camera {
|
||||||
|
ArgCamera::Default => 4.0,
|
||||||
|
ArgCamera::Far => 200.0,
|
||||||
|
};
|
||||||
|
|
||||||
|
commands.spawn((
|
||||||
|
Camera3d::default(),
|
||||||
|
Transform::from_xyz(0.0, 0.0, camera_distance).looking_at(Vec3::ZERO, Vec3::Y),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
fn play_animation(
|
||||||
|
trigger: Trigger<SceneInstanceReady>,
|
||||||
|
mut commands: Commands,
|
||||||
|
args: Res<Args>,
|
||||||
|
children: Query<&Children>,
|
||||||
|
animations_to_play: Query<&AnimationToPlay>,
|
||||||
|
mut players: Query<&mut AnimationPlayer>,
|
||||||
|
) {
|
||||||
|
if args.weights != ArgWeights::Animated {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Ok(animation_to_play) = animations_to_play.get(trigger.target()) {
|
||||||
|
for child in children.iter_descendants(trigger.target()) {
|
||||||
|
if let Ok(mut player) = players.get_mut(child) {
|
||||||
|
commands
|
||||||
|
.entity(child)
|
||||||
|
.insert(AnimationGraphHandle(animation_to_play.graph_handle.clone()));
|
||||||
|
|
||||||
|
player
|
||||||
|
.play(animation_to_play.index)
|
||||||
|
.repeat()
|
||||||
|
.set_speed(animation_to_play.speed);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_weights(
|
||||||
|
trigger: Trigger<SceneInstanceReady>,
|
||||||
|
args: Res<Args>,
|
||||||
|
children: Query<&Children>,
|
||||||
|
mut weight_components: Query<&mut MorphWeights>,
|
||||||
|
) {
|
||||||
|
let weight_value = match args.weights {
|
||||||
|
ArgWeights::One => Some(1.0),
|
||||||
|
ArgWeights::Zero => Some(0.0),
|
||||||
|
ArgWeights::Tiny => Some(0.00001),
|
||||||
|
_ => None,
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Some(weight_value) = weight_value {
|
||||||
|
for child in children.iter_descendants(trigger.target()) {
|
||||||
|
if let Ok(mut weight_component) = weight_components.get_mut(child) {
|
||||||
|
weight_component.weights_mut().fill(weight_value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user