Version: 2020.3
Developing for WebGL
WebGL:与浏览器脚本交互

WebGL 中的内存

Unity WebGL 中的内存限制可能会限制可以运行的内容的复杂性。

WebGL 内容在浏览器中运行。浏览器在其内存空间中分配应用程序运行内容所需的内存。 可用内存量因以下因素而异:

  • 使用的设备
  • 使用的操作系统
  • 使用的浏览器,以及是在 32 位还是 64 位处理器上运行
  • 浏览器的 JavaScript 引擎解析代码所需的内存量
  • 浏览器为每个选项卡使用单独进程,还是内容需要与所有其他打开的选项卡共享内存空间。

Unity WebGL 内容在几个方面需要浏览器分配大量内存。

Unity 堆

Unity 使用内存堆存储所有 Unity 引擎运行时对象。这些包括托管和原生对象、加载的资源、场景和着色器。这类似于 Unity 播放器在其他平台上使用的内存。

Unity 堆是分配的连续内存块。Unity 支持自动调整堆大小以满足应用程序的需要。堆大小可随着应用程序运行而扩展,最多可以扩展到 2GB。Unity 将此内存堆创建为内存对象。内存对象的缓冲区属性是可调整大小的 ArrayBuffer,用于保存 WebAssembly 代码访问的内存的原始字节。

如果浏览器无法在地址空间中分配连续的内存块,则自动调整堆大小可能会导致应用程序崩溃。出于此原因,应使 Unity 堆大小尽可能小,这十分重要。因此,在规划应用程序的内存使用量时要谨慎。如果要测试 Unity 堆的大小,可以使用性能分析器对内存块的内容进行性能分析。

资源数据

创建 Unity WebGL 构建时,Unity 会生成一个 .data 文件。这包含应用程序需要启动的所有场景和资源。因为 Unity WebGL 无法访问实际文件系统,所以会创建虚拟内存文件系统,浏览器会在此处解压缩 .data 文件。Emscipten 框架 (JavaScript) 在浏览器内存空间中分配此内存文件系统。内容运行时,浏览器内存会保留未压缩的数据。为了缩短下载时间和降低内存使用量,应尽量保持未压缩数据尽可能小。

为了减少内存使用量,可以将资源数据打包到 AssetBundles 中。通过 AssetBundles 可以完全控制资源下载。这意味着可以控制应用程序何时下载资源,以及运行时何时卸载它。卸载未使用的资源可释放内存。

AssetBundles 直接下载到 Unity 堆中,因此这些内容不会导致浏览器进行额外分配。

启用数据缓存可将内容中的资源数据自动缓存在用户计算机上。这意味着无需在以后的运行过程中重新下载该数据。Unity WebGL 加载程序使用 IndexedDB API 实现数据缓存。此选项使您可以缓存对于浏览器而言太大而无法在本机缓存的文件。

数据缓存使浏览器能够将应用程序数据存储在用户的计算机上。浏览器通常会限制可以在其缓存中存储的量以及可以缓存的最大文件大小。这通常不足以使应用程序顺畅运行。因此,Unity WebGL 加载程序使用 IndexedDB API 实现数据缓存。Unity 会将数据存储在 IndexedDB 中,而不是将内容存储在浏览器缓存中。

要启用数据缓存选项,请转到 File > Build Settings > Player Settings > Publishing Settings

Large-Allocation HTTP 标头

您的服务器可为您的内容发出 Large-Allocation HTTP 标头。这会向支持的浏览器(目前只有 Mozilla Firefox)告知您的内存需求。此信息使支持的浏览器可以使用未分段的内存空间生成新进程。浏览器还可以进行额外的内务处理以确保大型分配成功。 这可以解决浏览器在尝试分配 Unity 堆时内存不足的问题。这在 32 位浏览器上尤其重要。

垃圾收集注意事项

垃圾收集是查找和释放未使用内存的过程。一旦垃圾回收器收集了未使用的内存,它便会在 Unity 堆中重新分配这些内存。

For an overview on how Unity garbage collection works, see Automatic Memory Management. WebGL garbage collection runs when the stack is empty. The stack is a part of the Unity heap but not the heap itself. This usually occurs after every frame. This is different from the garbage collection process on other platforms, where the collector pauses all running threads so it can inspect the stack. This is not possible in JavaScript. You can debug the garbage collection process using the Unity Profiler.

在大多数其他平台上,垃圾收集会暂停所有正在运行的线程。垃圾回收器随后会检查其堆栈并注册加载的对象引用。目前在 JavaScript 中还无法实现这一点。因此,只有在堆栈为空的情况下,垃圾回收器才会在 WebGL 中运行。此操作当前在每一帧之后发生一次。

因此,以下代码将无法在 WebGL 上运行。这是因为回收器无法在循环的迭代之间运行垃圾回收器。这意味着垃圾回收器无法释放中间字符串对象使用的内存,可能会耗尽 Unity 堆中的内存。

string hugeString = "";
 
for (int i = 0; i < 100000; i++)
 
{
 
  hugeString += "foo";
 
}

在 2020.1 中引入了更新的 WebGL 加载程序

Developing for WebGL
WebGL:与浏览器脚本交互