加载优化

阅读时间:3分钟更新于 2025-05-29 11:50

耗时统计

加载耗时

执行 assetManager.loadScene 时会在控制台打印每个阶段的耗时情况,如:

[Galacean Effects] Load asset: totalTime: 270.8000ms [loadJSON: 74.80] [processJSON: 0.40] [processFontURL: 1.30] [plugin:processAssets: 2.00] [processBins: 73.80] [processImages: 194.00] [processTextures: 0.30] [plugin:prepareResource: 0.10], url: https://xxx.

具体含义如下:

  • totalTime: 加载总耗时
  • loadJSON: 加载 .json 的网络耗时
  • processJSON: 对 JSON 数据进行预处理
  • processBins: 加载 .bin 文件并进行预处理
  • processImages: 加载图片(含动态换图/视频、压缩纹理)文件并进行预处理
  • processFontURL: 加载 .ttf 的字体文件并进行预处理
  • plugin:processAssets: 插件生命周期的 processAssets,插件加载所需类型资源(如:音/视频资源)
  • processTextures: 图片相关资源转 Texture options
  • plugin:prepareResource: 插件生命周期的 prepareResource,插件对加载好的资源做进一步处理

首帧耗时

首帧耗时是指从加载到第一帧渲染的时间,也就是用户看到画面的耗时。

首帧耗时主要经过两个阶段:

  1. 相关资源的网络请求及初处理(即:加载耗时)。
  2. GPU 编译等,所有的 WebGL 程序在绘制之前都需要编译 Shader(编译耗时优化由 player 处理)。

Player 会在日志里记录第一帧耗时,当然你也可以自己测试:

interface CompositionStatistic {
  loadTime: number, // 资源加载时间
  compileTime: number, // Shader 编译耗时
  firstFrameTime: number, // 首帧耗时
}

player
  .loadScene('xx.json') // 资源加载
  .then(composition => {
    console.log(composition.statistic);
  });

// 打印结果如:
loadTime: 1062.1000000238419
compileTime: 18.5
firstFrameTime: 1250.699999988079

执行 player.loadScene 时会在控制台打印每个阶段的耗时情况,如:

[Galacean Effects] Shader async compile [新建合成3]: 9.6000ms.
[Galacean Effects] First frame [新建合成3]: 28.4000ms.

综上所述,加载耗时一般由网络状况决定,首帧耗时除了加载编译耗时,还会有一些资源处理耗时(如上图中的实例化合成/元素/GPU 纹理等)。

编译耗时与动效复杂性和设备性能有关,动效越复杂,所需要编译的 Shader 就越复杂,设备处理耗时越高。

为避免复杂动效在播放时的卡顿或延迟,可以对资源进行预加载并提前做编译

资源预加载

如上所示,一个动画的播放由资源载入和播放动画两部分组成,这两部分的耗时是没办法避免的。但是如果我们想继续优化动画播放的体验,这就需要我们将资源载入部分和播放动画拆分,提前将资源准备好,在播放的时候只需要播放动画就可以了,代码参考如下:

/*** === 资源准备阶段 === ***/
// 创建一个资源加载器
const assetManager = new AssetManager();
// 进行资源的加载
const scene = await assetManager.loadScene('xxx.json');
// 其他业务逻辑代码
/*** === 资源准备阶段 === ***/

/*** === 预编译准备阶段 === ***/
// 加载动画资源
await player.loadScene(scene, { autoplay: false });
/*** === 预编译准备阶段 === ***/

// 手动播放
player.play();

为保证渲染前所有所需资源都准备好,所以动态替换的图片也需要预加载,即:如果有动态换图(含动态视频),需要在初始化 AssetManager 时配置:

const assetManager = new AssetManager({
  variables: {
    image: 'https://xxx',
  },
});

await player.loadScene(scene, {
  variables: {
    text_3: 'Dynamic Text',
  },
});

注意:

  • 预加载仅对资源生效,所以,文本或富文本元素的动态替换在 AssetManager预加载中不会生效,请在 player.loadScene 中替换。
  • 资源准备阶段,可以不用准备 Canvas 画布,但是到了预编译准备阶段,就需要准备好 Canvas 画布,因为 shader 预编译需要 gl 对象。
  • 当我们需要在不同的 player 中播放时,请创建多个 AssetManager 对象进行资源加载,保证 Player 对象和 AssetManager 对象一一对应。

超时设置

有些场景对动画的出现时间要求很高,在没法做预加载的情况下,我们需要保证业务流程的顺畅,可以控制动画加载超时,如:1 秒加载完成并播放出来,如果此时间未能完成,可以及时用降级图兜底。

loadScene支撑直接设置 timeout,默认是 10 秒:

const player = new Player({
  container,
  onError: err => {
    player.dispose();
    player = null;
    // 超时的兜底处理
    console.error(err);
  },
});

// 700ms 内未完成加载会抛出超时错误
await player.loadScene(json, { timeout: 0.7 });

注意:

  • 非特殊情况,请不要自己加超时,可能会引起各种奇怪的报错,如下强烈不推荐:
// ❌ 不推荐
setTimeout(() => {
  player?.dispose();
  player = null;
  throw new Error('timeout');
}, 700);
Preview