跳到主要内容

MiniStats

MiniStats 是 PlayCanvas 应用的轻量性能面板,可在与场景交互时观察帧间隔、绘制调用、CPU/GPU 耗时和 GPU 资源内存估算。不需要面板、只通过代码采集时,请使用 AppStats。

本页介绍引擎 2.23 及之后版本的 MiniStats 界面。

启用 MiniStats​

编辑器用户可在 Launch 按钮菜单中启用:

编辑器 Launch 菜单中的 MiniStats 选项

独立应用在初始化应用后创建面板:

import { MiniStats } from 'playcanvas';

const miniStats = new MiniStats(app);

通过 script 标签加载引擎时,改用 new pc.MiniStats(app)。启用 GPU 计数器时,MiniStats 会启用 GPU 分析,但仍需满足设备支持要求。

显示尺寸​

点击分区标题之外的位置,可循环切换三种默认视图;也可以聚焦面板后按 Enter 或空格。

视图内容
Compact核心计数器与平均值。
Medium可折叠的 Engine、User、CPU、GPU 和 VRAM 分区,显示平均值,不显示图表和峰值列。
Large相同分区,并在文本后显示历史图表,同时显示平均值和峰值。

分区按 Engine、User、CPU、GPU、VRAM 排列。列表高于屏幕时,可用滚轮或触摸拖动。所有内容都在固定列标题下滚动,包括 Draw calls 和 Frame。

大视图中分区展开和折叠的效果如下:

展开折叠
展开 Engine、User、CPU、GPU 和 VRAM 分区的 MiniStatsMiniStats 各分区折叠,CPU、GPU 和 VRAM 总值仍显示

折叠分区​

中、大视图中,点击分区标题可隐藏或显示计数器。分区默认展开。折叠后标题仍显示,CPU、GPU、VRAM 也保留总值。Engine 和 User 的计数器单位可能不同,因此没有总值。

分区内容
Engine在 options.stats 中配置的内置计数器,默认包括 Draw calls 和 Frame。Update、FPS、图元数及高斯泼溅数量等也归此区。
User配置路径全部以 user. 开头、从 app.stats.user 读取的计数器。示例中只有 Wave 属于此区。
CPU、GPU、VRAM总值及可用的耗时或内存细分。

没有配置计数器时,Engine 和 User 不显示。折叠不会停止采样或历史记录,状态在尺寸切换后保留。但紧凑模式没有分区标题,直接显示计数器,忽略折叠状态。

以下可读写布尔属性控制同样的状态:

miniStats.engineCollapsed = true;
miniStats.userCollapsed = true;
miniStats.cpuCollapsed = true;
miniStats.gpuCollapsed = true;
miniStats.vramCollapsed = true;

// Expand GPU details again.
miniStats.gpuCollapsed = false;

五个属性默认均为 false,并与标题点击同步。紧凑模式下也可提前设置。折叠分区与 miniStats.enabled = false 不同,不会停止测量。

平均值、峰值与历史​

数字默认约每半秒刷新。Avg (0.5s) 是上次刷新后收集的帧样本算术平均值,Peak 是同一区间内最大样本,不是历史最大值。紧凑模式虽然不显示列标题,也使用相同平均窗口。

每次刷新后开始新窗口,并非连续滑动平均。textRefreshRate 以毫秒控制窗口,修改后标题也会变化。窗口在帧边界结束,因此可能比配置区间略长。

大视图每个应用帧都采集历史图表,与文本刷新独立,因此不同帧率下图表覆盖的时间长度不同。GPU 使用最新异步结果,等待新结果时可能重复采样旧值。

基础统计​

默认配置包含:

指标含义
Draw calls每帧提交的绘制命令,包括阴影、后期处理等附加通道,并非对象数量。
Frame应用 tick 之间的毫秒间隔,包括浏览器调度和引擎外等待,不只是引擎执行时间。稳定 60 FPS 约为 16.67 毫秒,30 FPS 约为 33.33 毫秒。
CPU跨引擎帧更新与渲染事件测得的 CPU 经过时间,不是 CPU 利用率百分比。
GPU设备支持时的整帧 GPU 经过时间,单位为毫秒;不是 GPU 利用率,也不是所有通道耗时之和。
VRAM被跟踪纹理和 GPU 缓冲区的内存估算。面板标为 MB,但每单位使用 1,048,576 字节,即 MiB。

CPU 与 GPU 工作可以重叠,两者耗时相加不等于 Frame。MiniStats 总 CPU 计时边界也不同于 AppStats.cpuUpdateTime 和 cpuRenderTime,两者之和不一定等于面板的 CPU 值。

详细计时模式​

在中、大视图展开 CPU、GPU 和 VRAM 后,会显示总值和可用子项。

CPU 子阶段耗时​

行含义
Render准备和提交渲染的 CPU 时间,包括 prerender/postrender 监听器、层级同步和批处理,不包含 GPU 执行。
Script update组件系统更新阶段,包括脚本 update、物理和其他订阅系统,不仅是用户脚本。
Script post-update组件系统后更新阶段,包括脚本 postUpdate。
AnimationAnimComponentSystem 使用的专用动画更新阶段,旧版动画组件在系统更新阶段运行。
Physics最近一次物理步,包括同步和接触处理,通常已包含在 Script update 中。
Splat sortWorker 报告的高斯泼溅排序时间,独立于主线程 CPU 时间。

不要将全部行直接相加:阶段可能重叠或包含其他阶段,Worker 数据也来自另一线程。Animation、Physics 和 Splat sort 在出现正值后才显示。

GPU 通道耗时​

WebGPU 支持时间戳查询时,详细视图显示各渲染和计算通道的耗时。同名通道合并为一行,常见项包括 Forward、Downsample、Upsample 和 Compose,取决于场景与渲染配置。

WebGL 2 中,PlayCanvas 提供整帧耗时而非逐通道细分。这是 PlayCanvas WebGL 分析实现的限制,并非计时查询只能测量整帧。

GPU 结果异步到达,可能来自数帧之前。各通道执行区间可能重叠,因此总和不一定等于整帧 GPU 耗时。

显存细分​

行跟踪资源
TexturesGPU 纹理。
Geometry顶点和索引缓冲区。
Buffers统一和存储缓冲区,仅 WebGPU 显示此行。

这些是图形设备资源估算,设备可能由多个应用共享。不包含 JavaScript 堆内存、未跟踪的驱动开销或物理 GPU 总容量。各类缓冲区的字节数可通过 AppStats 内存 getter 获取。

GPU 计时要求​

后端要求
WebGL 2EXT_disjoint_timer_query_webgl2 扩展。
WebGPUtimestamp-query 适配器特性,可用时引擎自动请求。

启用面板不会让设备获得本来不支持的能力。不可用的 GPU 测量可能在 MiniStats 中显示为零,不代表渲染没有 GPU 开销。没有有效测量时,app.stats.gpuFrameTime 返回 undefined。

默认 CPU 计时、绘制调用和内存估算支持所有构建,包括发布和压缩构建。部分附加计数器需要调试或 profiler 构建,参见支持列表。

定制面板​

建议先取得默认选项,修改后再构造 MiniStats。传入不完整选项对象不会自动与默认值合并。

import { MiniStats } from 'playcanvas';

const options = MiniStats.getDefaultOptions();
options.startSizeIndex = 2; // Large view
options.textRefreshRate = 500; // Averaging window in milliseconds
options.sizes[1].width = 190; // Medium panel width in CSS pixels
options.sizes[2].width = 260; // Large panel width in CSS pixels
options.cpu.watermark = 1000 / 60;
options.gpu.watermark = 1000 / 60;

const miniStats = new MiniStats(app, options);

每种尺寸都独立配置宽度、行高、间距、图表显示、详细模式和峰值。watermark 设置图表参考预算和纵轴比例;上面两个值标记 60 FPS 预算,不会限制实际执行时间。

附加计数器​

options.stats 中各项解析相对于 app.stats 的数字属性路径。尽可能使用公开的 AppStats getter。同一项配置多个路径时会相加。decimalPlaces 控制显示小数位,multiplier 缩放样本值,unitsName 指定单位标签。

例如,在创建面板前向选项添加:

options.stats.push({
name: 'Update',
stats: ['cpuUpdateTime'],
decimalPlaces: 1,
unitsName: 'ms',
watermark: 1000 / 60
});

// Primitive counts are available only in debug and profiler builds.
if (app.stats.primitiveCount !== undefined) {
options.stats.push({
name: 'Primitives',
stats: ['primitiveCount'],
decimalPlaces: 1,
multiplier: 1 / 1000,
unitsName: 'k',
watermark: 500
});
}

设置 miniStats.enabled = false 可隐藏面板并停止计数器采样,调用 miniStats.destroy() 可释放面板。GPU 分析是设备级设置,隐藏或销毁面板不会关闭;若没有其他工具需要,可在分析器存在时设置 app.graphicsDevice.gpuProfiler.enabled = false。

MiniStats 将面板合并到一个网格中,并复用文本图集。只有启用图表的视图收集历史记录。虽然开销经过控制,渲染和 GPU 查询仍有成本,前后对比时应保持检测配置一致。

全部选项和生命周期方法见 MiniStats API 参考。

用户计数器​

从引擎 2.23 起,app.stats.user 提供 Map<string, number>,保存应用定义的队列长度、手动 CPU 耗时等计数器。getter 始终返回同一个 Map,可通过 set、get、delete 和 clear 管理条目,所有引擎构建都支持。

创建 MiniStats 前,将图表添加到选项。user.wave 读取名为 wave 的 Map 条目;计数器名称应避免句点,因为句点用于分隔路径。下例显示一个在 0 到 20 之间振荡的值:

import { MiniStats } from 'playcanvas';

app.stats.user.set('wave', 10);

const options = MiniStats.getDefaultOptions();
options.startSizeIndex = 2; // Start with graph history visible
options.stats.push({
name: 'Wave',
stats: ['user.wave'],
decimalPlaces: 1,
watermark: 20
});
const miniStats = new MiniStats(app, options);

let time = 0;
app.on('update', (dt) => {
time += dt;
app.stats.user.set('wave', 10 + 10 * Math.sin(time));
});

引擎不会重置用户计数器。逐帧总量应先初始化,并在 frameupdate 中于累加前重置。测量同步代码时,可使用两次 performance.now() 的差值,并配置 unitsName: 'ms'。单位由应用定义,MiniStats 不会推断,也不会为新增 Map 条目自动添加图表。

示例​

资源分配示例会反复创建和释放实体、材质、顶点缓冲区及纹理。观察它们对计数器的影响,点击分区标题展开或折叠,再点击其他位置比较三种视图。

MiniStats 资源分配示例