Skip to content

Commit

Permalink
feat: add vello diagnostics plugin (#120)
Browse files Browse the repository at this point in the history
Closes #117
  • Loading branch information
simbleau authored Feb 19, 2025
1 parent 6d34b26 commit 9088119
Show file tree
Hide file tree
Showing 13 changed files with 356 additions and 117 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ This release supports Bevy version 0.14 and has an [MSRV][] of 1.80.
### Added

- Added `VelloView` marker component used for identifying cameras rendering vello content.
- Added `VelloEntityCountDiagnosticsPlugin` which can be used to provide vello entity type data at runtime. See the `diagnostics` example.
- Added `VelloFrameProfileDiagnosticsPlugin` which can be used to provide vello frame profile data at runtime. See the `diagnostics` example.

### Changed

Expand Down
8 changes: 8 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ members = [
"examples/render_layers",
"examples/cube3d",
"examples/headless",
"examples/diagnostics",
]

[workspace.package]
Expand Down
12 changes: 12 additions & 0 deletions examples/diagnostics/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
[package]
name = "diagnostics"
version.workspace = true
license.workspace = true
edition.workspace = true
repository.workspace = true
publish = false

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
bevy_vello = { path = "../../" }
bevy = { workspace = true }
99 changes: 99 additions & 0 deletions examples/diagnostics/src/main.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
use bevy::{diagnostic::DiagnosticsStore, prelude::*};
use bevy_vello::{
diagnostics::VelloEntityCountDiagnosticsPlugin,
diagnostics::VelloFrameProfileDiagnosticsPlugin, prelude::*, VelloPlugin,
};

const SCENE_COUNT: usize = 5;

fn main() {
let mut app = App::new();
app.add_plugins(DefaultPlugins)
.add_plugins(VelloPlugin::default())
.add_plugins(VelloEntityCountDiagnosticsPlugin)
.add_plugins(VelloFrameProfileDiagnosticsPlugin)
.add_systems(Startup, setup)
.add_systems(Update, simple_animation)
.add_systems(Update, update_scene_count_ui);

app.run();
}

fn setup(mut commands: Commands) {
commands.spawn((Camera2d, VelloView));
for i in 0..SCENE_COUNT {
commands.spawn((
VelloScene::new(),
Transform::from_translation(Vec3::new(i as f32 * 100.0 - 200.0, 0.0, 0.0)),
));
}

// UI Text displaying the scene count
commands.spawn((
Text::new("Total Scenes: 0"),
TextFont {
font_size: 30.0,
..default()
},
TextColor(Color::WHITE),
Node {
position_type: PositionType::Absolute,
left: Val::Px(10.0),
top: Val::Px(10.0),
..default()
},
DiagnosticsText,
));
}

#[derive(Component)]
struct DiagnosticsText;

fn simple_animation(mut query: Query<(&mut Transform, &mut VelloScene)>, time: Res<Time>) {
let sin_time = time.elapsed_secs().sin().mul_add(0.5, 0.5);

for (mut transform, mut scene) in query.iter_mut() {
scene.reset();

let c = Vec3::lerp(
Vec3::new(-1.0, 1.0, -1.0),
Vec3::new(-1.0, 1.0, 1.0),
sin_time + 0.5,
);
scene.fill(
peniko::Fill::NonZero,
kurbo::Affine::default(),
peniko::Color::new([c.x, c.y, c.z, 1.]),
None,
&kurbo::RoundedRect::new(-50.0, -50.0, 50.0, 50.0, (sin_time as f64) * 50.0),
);

transform.scale = Vec3::lerp(Vec3::splat(0.5), Vec3::ONE, sin_time);
transform.translation.y = sin_time * 50.0;
transform.rotation = Quat::from_rotation_z(-std::f32::consts::TAU * sin_time);
}
}

fn update_scene_count_ui(
diagnostics: Res<DiagnosticsStore>,
mut text: Single<&mut Text, With<DiagnosticsText>>,
) {
let Some(scenes) = diagnostics.get(&VelloEntityCountDiagnosticsPlugin::SCENE_COUNT) else {
return;
};
let Some(scene_count) = scenes.measurement() else {
return;
};
let Some(path_segs) = diagnostics.get(&VelloFrameProfileDiagnosticsPlugin::PATH_SEGMENTS_COUNT)
else {
return;
};
let Some(path_segs_count) = path_segs.measurement() else {
return;
};

text.0 = format!(
"Total scenes: {}\nTotal path segments: {}",
scene_count.value, path_segs_count.value
);
}
71 changes: 71 additions & 0 deletions src/diagnostics.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
use crate::render::{VelloEntityCountData, VelloFrameProfileData};
use bevy::{
diagnostic::{Diagnostic, DiagnosticPath, Diagnostics, RegisterDiagnostic},
prelude::*,
};

/// Adds Vello entity counting diagnostics to an App.
#[derive(Default)]
pub struct VelloEntityCountDiagnosticsPlugin;

impl Plugin for VelloEntityCountDiagnosticsPlugin {
fn build(&self, app: &mut App) {
app.register_diagnostic(Diagnostic::new(Self::SCENE_COUNT).with_suffix(" scenes"))
.register_diagnostic(Diagnostic::new(Self::TEXT_COUNT).with_suffix(" texts"))
.add_systems(Update, Self::diagnostic_system);

#[cfg(feature = "svg")]
app.register_diagnostic(Diagnostic::new(Self::SVG_COUNT).with_suffix(" svgs"));
#[cfg(feature = "lottie")]
app.register_diagnostic(Diagnostic::new(Self::LOTTIE_COUNT).with_suffix(" lotties"));
}
}

impl VelloEntityCountDiagnosticsPlugin {
pub const SCENE_COUNT: DiagnosticPath = DiagnosticPath::const_new("vello_scenes");
pub const TEXT_COUNT: DiagnosticPath = DiagnosticPath::const_new("vello_texts");
#[cfg(feature = "svg")]
pub const SVG_COUNT: DiagnosticPath = DiagnosticPath::const_new("vello_svgs");
#[cfg(feature = "lottie")]
pub const LOTTIE_COUNT: DiagnosticPath = DiagnosticPath::const_new("vello_lotties");

fn diagnostic_system(mut diagnostics: Diagnostics, data: Res<VelloEntityCountData>) {
diagnostics.add_measurement(&Self::SCENE_COUNT, || data.n_scenes as f64);
diagnostics.add_measurement(&Self::TEXT_COUNT, || data.n_texts as f64);
#[cfg(feature = "svg")]
diagnostics.add_measurement(&Self::SVG_COUNT, || data.n_svgs as f64);
#[cfg(feature = "lottie")]
diagnostics.add_measurement(&Self::LOTTIE_COUNT, || data.n_lotties as f64);
}
}

/// Adds Vello frame profile diagnostics to an App.
#[derive(Default)]
pub struct VelloFrameProfileDiagnosticsPlugin;

impl Plugin for VelloFrameProfileDiagnosticsPlugin {
fn build(&self, app: &mut App) {
app.register_diagnostic(Diagnostic::new(Self::PATH_COUNT).with_suffix(" paths"))
.register_diagnostic(
Diagnostic::new(Self::PATH_SEGMENTS_COUNT).with_suffix(" path segments"),
)
.register_diagnostic(Diagnostic::new(Self::CLIPS_COUNT).with_suffix(" clips"))
.register_diagnostic(Diagnostic::new(Self::OPEN_CLIPS_COUNT).with_suffix(" open clips"))
.add_systems(Update, Self::diagnostic_system);
}
}

impl VelloFrameProfileDiagnosticsPlugin {
pub const PATH_COUNT: DiagnosticPath = DiagnosticPath::const_new("vello_paths");
pub const PATH_SEGMENTS_COUNT: DiagnosticPath =
DiagnosticPath::const_new("vello_path_segments");
pub const CLIPS_COUNT: DiagnosticPath = DiagnosticPath::const_new("vello_clips");
pub const OPEN_CLIPS_COUNT: DiagnosticPath = DiagnosticPath::const_new("vello_open_clips");

fn diagnostic_system(mut diagnostics: Diagnostics, data: Res<VelloFrameProfileData>) {
diagnostics.add_measurement(&Self::PATH_COUNT, || data.n_paths as f64);
diagnostics.add_measurement(&Self::PATH_SEGMENTS_COUNT, || data.n_path_segs as f64);
diagnostics.add_measurement(&Self::CLIPS_COUNT, || data.n_clips as f64);
diagnostics.add_measurement(&Self::OPEN_CLIPS_COUNT, || data.n_open_clips as f64);
}
}
63 changes: 33 additions & 30 deletions src/integrations/lottie/render.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ use super::{
use crate::{
render::{
prepare::{PrepareRenderInstance, PreparedAffine, PreparedTransform},
VelloFrameData, VelloView,
VelloEntityCountData, VelloView,
},
CoordinateSpace, SkipEncoding,
};
Expand Down Expand Up @@ -56,17 +56,16 @@ pub fn extract_lottie_assets(
>,
>,
assets: Extract<Res<Assets<VelloLottie>>>,
mut frame_data: ResMut<VelloFrameData>,
mut frame_data: ResMut<VelloEntityCountData>,
) {
let mut n_lotties = 0;

// Respect camera ordering
let mut views: Vec<(&ExtractedCamera, Option<&RenderLayers>)> =
query_views.into_iter().collect();
views.sort_by(|(camera_a, _), (camera_b, _)| camera_a.order.cmp(&camera_b.order));
// Sort cameras by rendering order
let mut views: Vec<_> = query_views.iter().collect();
views.sort_unstable_by_key(|(camera, _)| camera.order);

for (
asset,
asset_handle,
asset_anchor,
coord_space,
transform,
Expand All @@ -78,29 +77,33 @@ pub fn extract_lottie_assets(
inherited_visibility,
) in query_vectors.iter()
{
if let Some(asset) = assets.get(asset.id()) {
if view_visibility.get() && inherited_visibility.get() {
let svg_render_layers = render_layers.unwrap_or_default();
for (_, camera_render_layers) in views.iter() {
if svg_render_layers.intersects(camera_render_layers.unwrap_or_default()) {
let playhead = playhead.frame();
commands
.spawn(ExtractedLottieAsset {
asset: asset.to_owned(),
transform: *transform,
asset_anchor: *asset_anchor,
theme: theme.cloned(),
render_mode: *coord_space,
playhead,
alpha: asset.alpha,
ui_node: ui_node.cloned(),
})
.insert(TemporaryRenderEntity);
n_lotties += 1;
break;
}
}
}
// Skip if visibility conditions are not met
if !view_visibility.get() || !inherited_visibility.get() {
continue;
}
// Skip if asset isn't loaded.
let Some(asset) = assets.get(asset_handle.id()) else {
continue;
};

// Check if any camera renders this asset
let asset_render_layers = render_layers.unwrap_or_default();
if views.iter().any(|(_, camera_layers)| {
asset_render_layers.intersects(camera_layers.unwrap_or_default())
}) {
commands
.spawn(ExtractedLottieAsset {
asset: asset.clone(),
transform: *transform,
asset_anchor: *asset_anchor,
theme: theme.cloned(),
render_mode: *coord_space,
playhead: playhead.frame(),
alpha: asset.alpha,
ui_node: ui_node.cloned(),
})
.insert(TemporaryRenderEntity);
n_lotties += 1;
}
}

Expand Down
58 changes: 31 additions & 27 deletions src/integrations/svg/render.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ use crate::{
prelude::*,
render::{
prepare::{PrepareRenderInstance, PreparedAffine, PreparedTransform},
VelloFrameData,
VelloEntityCountData,
},
};
use bevy::{
Expand Down Expand Up @@ -52,17 +52,16 @@ pub fn extract_svg_assets(
>,
>,
assets: Extract<Res<Assets<VelloSvg>>>,
mut frame_data: ResMut<VelloFrameData>,
mut frame_data: ResMut<VelloEntityCountData>,
) {
let mut n_svgs = 0;

// Respect camera ordering
let mut views: Vec<(&ExtractedCamera, Option<&RenderLayers>)> =
query_views.into_iter().collect();
views.sort_by(|(camera_a, _), (camera_b, _)| camera_a.order.cmp(&camera_b.order));
// Sort cameras by rendering order
let mut views: Vec<_> = query_views.iter().collect();
views.sort_unstable_by_key(|(camera, _)| camera.order);

for (
asset,
asset_handle,
asset_anchor,
coord_space,
transform,
Expand All @@ -72,26 +71,31 @@ pub fn extract_svg_assets(
inherited_visibility,
) in query_vectors.iter()
{
if let Some(asset) = assets.get(asset.id()) {
if view_visibility.get() && inherited_visibility.get() {
let svg_render_layers = render_layers.unwrap_or_default();
for (_, camera_render_layers) in views.iter() {
if svg_render_layers.intersects(camera_render_layers.unwrap_or_default()) {
commands
.spawn(ExtractedVelloSvg {
asset: asset.to_owned(),
transform: *transform,
asset_anchor: *asset_anchor,
render_mode: *coord_space,
ui_node: ui_node.cloned(),
alpha: asset.alpha,
})
.insert(TemporaryRenderEntity);
n_svgs += 1;
break;
}
}
}
// Skip if visibility conditions are not met
if !view_visibility.get() || !inherited_visibility.get() {
continue;
}
// Skip if asset isn't loaded.
let Some(asset) = assets.get(asset_handle.id()) else {
continue;
};

// Check if any camera renders this asset
let asset_render_layers = render_layers.unwrap_or_default();
if views.iter().any(|(_, camera_layers)| {
asset_render_layers.intersects(camera_layers.unwrap_or_default())
}) {
commands
.spawn(ExtractedVelloSvg {
asset: asset.to_owned(),
transform: *transform,
asset_anchor: *asset_anchor,
render_mode: *coord_space,
ui_node: ui_node.cloned(),
alpha: asset.alpha,
})
.insert(TemporaryRenderEntity);
n_svgs += 1;
}
}

Expand Down
1 change: 1 addition & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ mod plugin;
pub use plugin::VelloPlugin;

pub mod debug;
pub mod diagnostics;
pub mod integrations;
pub mod render;
pub mod text;
Expand Down
Loading

0 comments on commit 9088119

Please sign in to comment.