Skip to content

Commit 2bb3d1b

Browse files
ryantrembghgary
andauthored
Optimize glTF animation loading (#17043)
This is a first pass at some low hanging fruit for optimizing glTF animation loading perf. These changes together reduce animation loading time by about 33% (in my test model with what turns into ~90 Babylon animation groups, total time to load animations went from 270ms to 180ms). The main changes are: - Reduce some unnecessary asynchrony (e.g. `return away Promise.resolve()`) - Remove `async` from a function that then no longer needed it (which also improves perf) - Cache the promises returned from dynamic imports The rest of the changes are adding some additional perf markers in the glTF loader around animations, and adding a couple extra glTF loader options about enabling perf logging and performing glTF validation. The changes look bigger than they are because this PR started from a branch from @bghgary where he re-ordered options to be alphabetical. :) --------- Co-authored-by: Gary Hsu <[email protected]>
1 parent 7aed708 commit 2bb3d1b

File tree

2 files changed

+170
-130
lines changed

2 files changed

+170
-130
lines changed

packages/dev/loaders/src/glTF/2.0/glTFLoader.ts

Lines changed: 59 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,10 @@ import { GetMappingForKey } from "./Extensions/objectModelMapping";
8484
import { deepMerge } from "core/Misc/deepMerger";
8585
import { GetTypedArrayConstructor } from "core/Buffers/bufferUtils";
8686

87+
// Caching these dynamic imports gives a surprising perf boost (compared to importing them directly each time).
88+
let AnimationGroupModulePromise: Nullable<Promise<typeof import("core/Animations/animationGroup")>> = null;
89+
let LoaderAnimationPromise: Nullable<Promise<typeof import("./glTFLoaderAnimation")>> = null;
90+
8791
export { GLTFFileLoader };
8892

8993
interface ILoaderProperty extends IProperty {
@@ -1615,6 +1619,8 @@ export class GLTFLoader implements IGLTFLoader {
16151619
}
16161620

16171621
private _loadAnimationsAsync(): Promise<void> {
1622+
this._parent._startPerformanceCounter("Load animations");
1623+
16181624
const animations = this._gltf.animations;
16191625
if (!animations) {
16201626
return Promise.resolve();
@@ -1634,7 +1640,9 @@ export class GLTFLoader implements IGLTFLoader {
16341640
);
16351641
}
16361642

1637-
return Promise.all(promises).then(() => {});
1643+
return Promise.all(promises).then(() => {
1644+
this._parent._endPerformanceCounter("Load animations");
1645+
});
16381646
}
16391647

16401648
/**
@@ -1644,13 +1652,19 @@ export class GLTFLoader implements IGLTFLoader {
16441652
* @returns A promise that resolves with the loaded Babylon animation group when the load is complete
16451653
*/
16461654
public loadAnimationAsync(context: string, animation: IAnimation): Promise<AnimationGroup> {
1655+
this._parent._startPerformanceCounter("Load animation");
1656+
16471657
const promise = this._extensionsLoadAnimationAsync(context, animation);
16481658
if (promise) {
16491659
return promise;
16501660
}
16511661

1662+
if (!AnimationGroupModulePromise) {
1663+
AnimationGroupModulePromise = import("core/Animations/animationGroup");
1664+
}
1665+
16521666
// eslint-disable-next-line @typescript-eslint/naming-convention
1653-
return import("core/Animations/animationGroup").then(({ AnimationGroup }) => {
1667+
return AnimationGroupModulePromise.then(({ AnimationGroup }) => {
16541668
this._babylonScene._blockEntityCollection = !!this._assetContainer;
16551669
const babylonAnimationGroup = new AnimationGroup(animation.name || `animation${animation.index}`, this._babylonScene);
16561670
babylonAnimationGroup._parentContainer = this._assetContainer;
@@ -1672,6 +1686,8 @@ export class GLTFLoader implements IGLTFLoader {
16721686
);
16731687
}
16741688

1689+
this._parent._endPerformanceCounter("Load animation");
1690+
16751691
return Promise.all(promises).then(() => {
16761692
babylonAnimationGroup.normalize(0);
16771693
return babylonAnimationGroup;
@@ -1689,7 +1705,7 @@ export class GLTFLoader implements IGLTFLoader {
16891705
* @param onLoad Called for each animation loaded
16901706
* @returns A void promise that resolves when the load is complete
16911707
*/
1692-
public async _loadAnimationChannelAsync(
1708+
public _loadAnimationChannelAsync(
16931709
context: string,
16941710
animationContext: string,
16951711
animation: IAnimation,
@@ -1698,11 +1714,11 @@ export class GLTFLoader implements IGLTFLoader {
16981714
): Promise<void> {
16991715
const promise = this._extensionsLoadAnimationChannelAsync(context, animationContext, animation, channel, onLoad);
17001716
if (promise) {
1701-
return await promise;
1717+
return promise;
17021718
}
17031719

17041720
if (channel.target.node == undefined) {
1705-
return await Promise.resolve();
1721+
return Promise.resolve();
17061722
}
17071723

17081724
const targetNode = ArrayItem.Get(`${context}/target/node`, this._gltf.nodes, channel.target.node);
@@ -1711,49 +1727,54 @@ export class GLTFLoader implements IGLTFLoader {
17111727

17121728
// Ignore animations that have no animation targets.
17131729
if ((pathIsWeights && !targetNode._numMorphTargets) || (!pathIsWeights && !targetNode._babylonTransformNode)) {
1714-
return await Promise.resolve();
1730+
return Promise.resolve();
17151731
}
17161732

17171733
// Don't load node animations if disabled.
17181734
if (!this._parent.loadNodeAnimations && !pathIsWeights && !targetNode._isJoint) {
1719-
return await Promise.resolve();
1735+
return Promise.resolve();
17201736
}
1721-
// async-load the animation sampler to provide the interpolation of the channelTargetPath
1722-
await import("./glTFLoaderAnimation");
17231737

1724-
let properties: IInterpolationPropertyInfo[];
1725-
switch (channelTargetPath) {
1726-
case AnimationChannelTargetPath.TRANSLATION: {
1727-
properties = GetMappingForKey("/nodes/{}/translation")?.interpolation!;
1728-
break;
1729-
}
1730-
case AnimationChannelTargetPath.ROTATION: {
1731-
properties = GetMappingForKey("/nodes/{}/rotation")?.interpolation!;
1732-
break;
1733-
}
1734-
case AnimationChannelTargetPath.SCALE: {
1735-
properties = GetMappingForKey("/nodes/{}/scale")?.interpolation!;
1736-
break;
1737-
}
1738-
case AnimationChannelTargetPath.WEIGHTS: {
1739-
properties = GetMappingForKey("/nodes/{}/weights")?.interpolation!;
1740-
break;
1738+
if (!LoaderAnimationPromise) {
1739+
LoaderAnimationPromise = import("./glTFLoaderAnimation");
1740+
}
1741+
1742+
// async-load the animation sampler to provide the interpolation of the channelTargetPath
1743+
return LoaderAnimationPromise.then(() => {
1744+
let properties: IInterpolationPropertyInfo[];
1745+
switch (channelTargetPath) {
1746+
case AnimationChannelTargetPath.TRANSLATION: {
1747+
properties = GetMappingForKey("/nodes/{}/translation")?.interpolation!;
1748+
break;
1749+
}
1750+
case AnimationChannelTargetPath.ROTATION: {
1751+
properties = GetMappingForKey("/nodes/{}/rotation")?.interpolation!;
1752+
break;
1753+
}
1754+
case AnimationChannelTargetPath.SCALE: {
1755+
properties = GetMappingForKey("/nodes/{}/scale")?.interpolation!;
1756+
break;
1757+
}
1758+
case AnimationChannelTargetPath.WEIGHTS: {
1759+
properties = GetMappingForKey("/nodes/{}/weights")?.interpolation!;
1760+
break;
1761+
}
1762+
default: {
1763+
throw new Error(`${context}/target/path: Invalid value (${channel.target.path})`);
1764+
}
17411765
}
1742-
default: {
1743-
throw new Error(`${context}/target/path: Invalid value (${channel.target.path})`);
1766+
// stay safe
1767+
if (!properties) {
1768+
throw new Error(`${context}/target/path: Could not find interpolation properties for target path (${channel.target.path})`);
17441769
}
1745-
}
1746-
// stay safe
1747-
if (!properties) {
1748-
throw new Error(`${context}/target/path: Could not find interpolation properties for target path (${channel.target.path})`);
1749-
}
17501770

1751-
const targetInfo: IObjectInfo<IInterpolationPropertyInfo[]> = {
1752-
object: targetNode,
1753-
info: properties,
1754-
};
1771+
const targetInfo: IObjectInfo<IInterpolationPropertyInfo[]> = {
1772+
object: targetNode,
1773+
info: properties,
1774+
};
17551775

1756-
return await this._loadAnimationChannelFromTargetInfoAsync(context, animationContext, animation, channel, targetInfo, onLoad);
1776+
return this._loadAnimationChannelFromTargetInfoAsync(context, animationContext, animation, channel, targetInfo, onLoad);
1777+
});
17571778
}
17581779

17591780
/**

0 commit comments

Comments
 (0)