开源一个Cesium3DTiles的显存管理工具

起因

接到个需求:解决低配置电脑中,浏览 3DTiles 容易爆显存,导致 WebGL 崩溃的问题。

简单翻看了一下 Cesium3DTiles 的显存管理机制,编写了一个管理工具。

合理的使用该管理工具,可以有效减少由于 3DTiles 占用的显存过大而导致爆显存的问题。

Cesium3DTiles 的显存管理

Cesium 中单个 Cesium3DTiles 有自己的显存控制参数 maximumMemoryUsage 。该参数可以控制单个 Cesium3DTiles 的显存消耗。

不过,值得注意的是,maximumMemoryUsage 并不是一个强制性的值,它仅仅起到一个阈值的作用。也就是当场景中 Cesium3DTiles 使用的显存超过该值时,就会触发缓存回收机制。而缓存回收后,使用的总显存并不一定会降到该值以下。比如,在当前相机视角下,3DTiles 需要 300MB 才能达到对应的显示精度,而最大使用显存设置到 256MB 时,最终使用的显存将以 Cesium 需要的显存为准,也就是 300MB。

maximumMemoryUsage

maximumMemoryUsage 背后的工作原理其实很简单:

  1. 获取当前消耗的显存数量 totalMemoryUsageInBytes (单位Byte)
  2. 判断消耗的显存是否大于 maximumMemoryUsage
  3. 如果大于,则回收缓存 trimLoadedTiles (回收上一帧中未使用的瓦片缓存)

具体实现

了解了单个 Cesium3DTiles 如何管理显存,那管理全部的 Cesium3DTiles 就简单多了。

直接上代码

代码使用 TypeScript 编写,如果想使用 JavaScript 版本,请自行用 tsc 编译,或者手动修改成 JavaScript 版(代码并不复杂)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
import * as Cesium from "cesium";
export default class Cesium3DTilesMaximumTotalMemoryUsageControl {
private _scene: Cesium.Scene;
private _current3DTilesTotalMemoryUsage: number;

/**
* Cesium3DTiles的最大使用显存(单位MB)
*
* @type {Number}
*
*/
public maximumTotalMemoryUsage: number;

/**
* 创建Cesium3DTiles显存管理器<br>
*
* <pre>
* 该显存管理器可管理整个场景中,Cesium3DTiles使用的总显存数量。
* 合理的使用该管理器可以有效的减少甚至避免 WebGL 由于爆显存而导致的报错。
*
* 值得注意的是:
* 这里设置的最大使用显存并不是一个强制性的值,它仅仅起到一个阈值的作用。
* 也就是当场景中 3DTiles 使用的显存超过该值时,才会触发缓存回收机制。
* 而缓存回收后,使用的总显存并不一定会降到该值以下。
* 比如,在当前相机视角下,3DTiles 需要 300MB 才能达到对应的显示精度,
* 而最大使用显存设置到 200MB 时,最终使用的显存将以 Cesium 需要的显存为准,也就是 300MB。
* <pre>
*
* @constructor
* @alias Cesium3DTilesMaximumTotalMemoryUsageControl
*
* @param {Cesium.Scene} scene Cesium的Scene对象
* @param {Number} maximumTotalMemoryUsage Cesium3DTiles的最大使用显存(单位MB)
*
*/
constructor(scene: Cesium.Scene, maximumTotalMemoryUsage: number) {
this._scene = scene;
this.maximumTotalMemoryUsage = maximumTotalMemoryUsage;
this._current3DTilesTotalMemoryUsage = 0;

// tileLoad时获取新的内存占用
(this._scene.primitives as any)._primitives.forEach((primitive: any) => {
if (primitive instanceof Cesium.Cesium3DTileset) {
primitive.tileLoad.addEventListener(this._preRender);
}
});
}

/**
* 当前场景中Cesium3DTiles使用的显存总数(单位MB)
*
* @type {Number}
* @readonly
*/
get current3DTilesTotalMemoryUsage() {
return this._current3DTilesTotalMemoryUsage;
}

private _preRender = () => {
// 计算当前使用的显存总数
this._current3DTilesTotalMemoryUsage = 0;
(this._scene.primitives as any)._primitives.forEach((primitive: any) => {
if (primitive instanceof Cesium.Cesium3DTileset) {
this._current3DTilesTotalMemoryUsage +=
primitive.totalMemoryUsageInBytes / 1024 / 1024;
}
});

// 如果显存总数超过设置的阈值
if (this._current3DTilesTotalMemoryUsage > this.maximumTotalMemoryUsage) {
// 手动释放上一帧未选择的所有瓦片
(this._scene.primitives as any)._primitives.forEach((primitive: any) => {
if (primitive instanceof Cesium.Cesium3DTileset) {
primitive.trimLoadedTiles();
}
});
}
};

/**
* 销毁释放内存
*/
destroy() {
(this._scene.primitives as any)._primitives.forEach((primitive: any) => {
if (primitive instanceof Cesium.Cesium3DTileset) {
primitive.tileLoad.removeEventListener(this._preRender);
}
});
Cesium.destroyObject(this);
}
}

使用方法

1
2
3
4
5
6
7
8
9
10
11
12
13

// 创建显存管理工具
const memoryUsageControl = new Cesium3DTilesMaximumTotalMemoryUsageControl(scene, 1024);

// 更新最大显存
memoryUsageControl.maximumTotalMemoryUsage = 2048;

// 获取当前使用的总显存
console.log(memoryUsageControl.current3DTilesTotalMemoryUsage);

// 销毁显存管理工具
memoryUsageControl.destroy();

注意事项

值得注意的是,这里的最大显存和 Cesium3DTileset 的 maximumMemoryUsage 一样并不是一个强制性的值,不能保证消耗的显存一定会降到该值以下!!

创建该工具的时候,为了减少不必要的计算,手动为场景中的所有 3DTiles 注册了 tileLoad 事件。所以需要注意,当新的3DTiles加入时,新的3DTiles没有注册对应的事件,会导致新的3DTiles使用的显存不计入该工具记录的总显存中。


开源一个Cesium3DTiles的显存管理工具
https://www.liaomz.top/2022/12/05/kai-yuan-yi-ge-cesium3dtiles-de-xian-cun-guan-li-gong-ju/
作者
发布于
2022年12月5日
许可协议