优化 WebGL

由于 WebGL 需要执行额外的验证以确保 Web 安全性,与原生 OpenGL 应用程序相比,运行 WebGL 应用程序的 CPU 侧开销已知更高。因此,当与 GL 函数交互时,移植图形密集型应用程序可能会在 CPU 侧成为瓶颈。因此,为了获得最佳性能,应特别注意分析和优化预期为 WebGL 密集型应用程序中的 GL API 使用情况。本优化指南重点介绍了已发现对提高 WebGL 性能有用的不同技术。

那里有如此多的 GL 硬件和驱动程序供应商,以及操作系统组合,以至于难以生成特定的优化指南。在某些硬件/驱动程序/操作系统组合上效率很高的某些优化已被证明对来自另一个供应商的驱动程序没有太大区别。幸运的是,很少发现冲突的场景,其中针对一种驱动程序的特定优化会导致在来自另一个 GPU 供应商的硬件上发生性能下降。大多数情况下,当这种情况发生时,是由于特定硬件不支持特定功能,导致驱动程序诉诸模拟。例如,在一个案例中发现,原生 GL 驱动程序宣传支持 ETC2 压缩纹理格式,即使图形硬件没有实现它,而在另一个案例中,发现使用顶点着色器原始重启索引会导致 GL 驱动程序回退到在软件中运行顶点着色器。不幸的是,OpenGL 规范没有为驱动程序提供报告这些类型性能警告的方法,这就是为什么在优化 GL 时几乎需要能够跨大量目标硬件进行基准测试的原因。在运行时密切注意浏览器的网页控制台也很有用,因为浏览器可以在控制台日志中报告额外的性能警告。

还应该承认,一些检测到的性能问题是由于浏览器及其使用的软件库中的低效率或彻头彻尾的性能错误造成的,与底层 GL 驱动程序或 Web 安全性本身无关。在最初致力于优化 Emscripten 移植的 GL 代码库时,发现大多数浏览器在他们的 WebGL 堆栈中效率低下,但这在预期之中,因为在 Emscripten 和 asm.js 之前,甚至不可能执行如此精确的 GL 性能比较在原生与 web 之间,以至于大量性能关键问题都落入缝隙中。随着越来越多的人使用大型 Emscripten 代码库来对 WebGL 施加压力,这方面一直在稳步改善,因此本指南中的某些项目在将来可能不再相关。如果您在将来阅读本指南时发现了一些似乎在所有情况下都会造成净损失的事情,请务必提交一个文档 PR 以进行讨论。同样,如果某个 GL 访问模式在 web 上比原生模式慢几个数量级,那很可能是一个性能错误。

以下优化技巧列出了已知在实践中产生影响的不同情况,尽管建议不要盲目地执行任何优化,但在进行实验时请将分析器放在手边。

目标 GL 模式?

Emscripten 允许使用不同的链接器标志来定位各种不同的 OpenGL 和 OpenGL ES API 风格。

默认情况下,如果没有选择任何特殊的与 GL 相关的链接器标志,Emscripten 会定位 WebGL 1 API,用户代码通过在 C/C++ 代码中包含 OpenGL ES 2.0 头文件来访问该 API (#include <GLES2/gl2.h>#include <GLES2/gl2ext.h>)。此模式的工作方式类似于 GLES 2,区别在于应用了一些 WebGL 特定的更改和限制。有关 WebGL 1 和 OpenGL ES 2 之间差异的几乎完整参考,请参阅 WebGL 1 规范:WebGL 和 OpenGL ES 2.0 之间的差异

  • 如果您的应用程序从客户端内存渲染几何体,则需要使用链接器标志 -sFULL_ES2 进行构建。此模式便于简化新代码库的移植,但 WebGL 本身不支持从客户端内存进行渲染,因此此功能是模拟的。为了获得最佳性能,请改用 VBO,并使用链接器标志 -sFULL_ES2 进行构建。

  • 如果您的应用程序定位旧的桌面 OpenGL API,则它可能在使用 -sLEGACY_GL_EMULATION 标志构建时起作用。但是,当在此模式下构建时,即使它有效,也不要指望良好的性能。如果应用程序在此模式下速度很慢并且它仅使用固定管道而根本不使用着色器,那么也可以将 -sLEGACY_GL_EMULATION-sGL_FFP_ONLY 链接器标志配对以尝试恢复一些性能。虽然总的来说,建议花时间将应用程序移植到使用 WebGL 1/OpenGL ES 2 代替。

  • 在定位 OpenGL ES 3 时,如果需要从客户端内存渲染,或者需要使用 glMapBuffer*() API,请传递链接器标志 -sFULL_ES3 以模拟这些功能,而核心 WebGL 2 不具备这些功能。预期这种模拟会影响性能,因此建议改用 VBO。

  • 即使您的应用程序不需要任何 WebGL 2/OpenGL ES 3 功能,也请考虑将应用程序移植到 WebGL 2 上运行,因为 WebGL 2 中的 JavaScript 侧性能已优化为不生成任何临时垃圾,据观察,这可以带来 3-7% 的稳定速度提升,并减少渲染时可能出现的卡顿。要启用这些优化,请使用链接器标志 -sMAX_WEBGL_VERSION=2 进行构建,并确保在 GL 启动时创建 WebGL 2 上下文(如果使用 EGL,则为 OpenGL ES 3 上下文)。

如何分析 WebGL

可以使用多种工具来衡量 GL 性能。总的来说,这里建议开发人员不要将重点仅限于搜索特定于 Web 浏览器的分析工具,但在实践中发现原生分析器同样有效,甚至更好。使用原生分析器的唯一缺点是,可能需要对浏览器中 WebGL 的实现方式有深入了解,否则可能难以理解进入 GPU 的调用流。

  • 要概述在不同 WebGL 入口点中花费了多少时间,请使用 Firefox 及其 Gecko Profiler 附加组件。此分析工具能够显示整个已执行代码堆栈的计时数据:手写的 JavaScript、asm.js/WebAssembly 和原生的 Firefox C/C++ 浏览器代码,与其他分析工具相比,这使其非常宝贵。

  • 能够分析 CPU 开销的有用原生工具包括 AMD CodeXLIntel VTune AmplifiermacOS Instruments。如果浏览器性能分析工具表明在浏览器入口点本身内部花费了大量时间,那么这些工具在定位热点时可能会有用。但是,在使用原生 CPU 分析工具时,需要手动从源代码构建浏览器代码以获得符号信息数据(例如,Windows 上的 .pdb 文件),这些工具会在本地查找这些数据。以这种方式调试 Firefox 时,禁用 Firefox 中的多进程架构很有用,以便获取将内容进程与浏览器本身在同一线程中运行的跟踪。将 Firefox 浏览器导航到页面 about:config,并将 browser.tabs.remote.autostart.2 首选项设置为 false,然后重新启动浏览器。

  • 为了调试 GPU 侧 API 调用跟踪,NVidia Nsight英特尔图形性能分析器Visual Studio 图形调试器AMD CodeXL 是有用的工具。在 Windows 上,Firefox 可以使用 OpenGL 或 Direct3D 来渲染 WebGL 内容。Direct3D 是默认设置,但例如 AMD CodeXL 只能跟踪 OpenGL 调用流。为了使用 AMD CodeXL 跟踪 Firefox 中的 WebGL API 调用,请将浏览器导航到 about:config 并将 pref webgl.disable-angle 设置为 true,然后重新加载页面。

避免冗余调用

在 WebGL 中,每个 GL 函数调用都有一定的开销,即使那些看似简单且几乎没有作用的调用也是如此。这是因为 WebGL 实现需要验证每个调用,因为底层的原生 OpenGL 规范没有提供任何关于网络上可以依赖的安全保证。此外,在 asm.js/WebAssembly 侧,每个 WebGL 调用都会生成一个 FFI 转换(在 asm.js 上下文中执行代码和在浏览器原生 C++ 上下文中执行代码之间的跳转),这比 asm.js/WebAssembly 内部的一次普通函数调用开销略高。因此,在网络上,通常最好在 CPU 侧性能方面尽量减少对 WebGL 的调用次数。以下技巧可以应用于此。

  • 优化渲染器和输入资产以避免冗余调用。如果需要,重构设计,以便渲染器能够更好地判断哪些状态更改是相关的,哪些是不需要的。最好的缓存是无必要的缓存,因此,如果高级渲染器能够保持 GL 调用流精简,那么这将产生最快的结果。但是,在难以实现的情况下,一些低级缓存类型可能有效,将在下面讨论。

  • 在渲染器代码中缓存 GL 状态,并避免在状态未发生变化的情况下多次进行冗余调用来设置相同的状态。例如,一些引擎可能会在每次绘制调用之前盲目地重新配置深度测试或 Alpha 混合模式,或者为每次调用重置着色器程序。

  • 避免所有类型的渲染器模式,这些模式在某些操作之后将 GL 重置为某个特定的“基态”。常见的现象是在发出每个绘制调用后 glBindBuffer(GL_ARRAY_BUFFER, 0)glUseProgram(0)for(i in 0 -> max_attributes) glDisableVertexAttribArray(i);,以恢复到已知的固定配置。相反,在从一个绘制调用过渡到另一个绘制调用时,只懒惰地更改所需的 GL 状态。

  • 考虑仅在 GL 状态需要生效时才懒惰地设置它。例如,在以下调用流中

    // First draw
    glBindBuffer(...);
    glVertexAttribPointer(...);
    glActiveTexture(0);
    glBindTexture(GL_TEXTURE_2D, texture1);
    glActiveTexture(1);
    glBindTexture(GL_TEXTURE_2D, texture2);
    glDrawArrays(...);
    
    // Second draw (back-to-back)
    glBindBuffer(...);
    glVertexAttribPointer(...);
    glActiveTexture(0); // (*)
    glBindTexture(GL_TEXTURE_2D, texture1); // (*)
    glActiveTexture(1); // (*)
    glBindTexture(GL_TEXTURE_2D, texture2); // (*)
    glDrawArrays(...);
    

用星号标记的四个 API 调用都是冗余的,但简单的状态缓存不足以检测到这一点。更懒惰的状态缓存机制将能够检测到这些类型的更改。但是,在实现深度懒惰状态缓存时,建议仅在获得配置文件数据来推动优化之后才这样做,因为在渲染之前将懒惰缓存技术应用于所有 GL 状态也会因为其他原因而变得昂贵,并且如果渲染器已经擅长避免重新提交冗余调用,那么性能可能会浪费。适量的缓存可能需要一些调整才能找到平衡。

一个很好的经验法则是,一个从一开始就通过高级设计避免冗余状态调用的渲染器通常比一个在低级别上严重依赖状态缓存的渲染器效率更高。

最小化 API 调用的技术

除了删除完全冗余的 API 调用之外,还应注意如何使用其他技术来最小化状态更改。以下清单提供了一些可能性。

  • 在渲染到离屏渲染目标时,使用多个 FBO,这样切换渲染目标只需要一个 glBindFramebuffer() 调用。这避免了每帧执行多次调用来设置 FBO 状态。

  • 避免更改 FBO 状态,而是倾向于设置多个不可变/静态 FBO,这些 FBO 不改变状态。更改 FBO 状态会导致浏览器重新验证该 FBO 组合,但不可变 FBO 只需要在创建时验证一次。

  • 尽可能使用 VAO,避免多次调用 GL 函数来设置用于渲染的顶点属性。

  • 将 glUniform* 调用批处理到统一数组,并在一个 glUniform4fv() 数组调用中更新它们,而不是多次调用 glUniform4f() 来单独更新每一个。或者更好的是,在 WebGL 2 中使用统一缓冲区对象。

  • 不要在渲染时调用 glGetUniformLocation(),而是在启动时为每个着色器程序查询一次位置并缓存它们。

  • 在适用时使用实例渲染。

  • 考虑将多个纹理图集到一个纹理中,以实现更好的几何批处理和实例化机会。

  • 考虑与原生 GL 平台相比,更积极地剔除可渲染对象,如果还没有尽可能地紧凑。

避免 GPU-CPU 同步点

高效 GPU 使用的最重要方面是确保 CPU 在渲染时永远不需要阻塞 GPU,反之亦然。这些类型的停顿会产生非常昂贵的 CPU-GPU 同步点,从而导致两种资源的利用率低下。通常,可以通过观察整体 GPU 和 CPU 利用率来检测到这种情况发生的暗示。如果 GPU 分析器声称 GPU 在很大一部分时间内处于空闲状态,但 CPU 分析器声称 CPU 反过来处于空闲状态,或者某些 GL 函数需要很长时间才能完成,这表明帧没有有效地提交到 GPU,但 GPU-CPU 同步发生在绘制调用提交过程中的某个地方。不幸的是,OpenGL 规范没有提供任何性能保证,说明哪些 GL 调用可能会导致停顿,因此要注意以下行为,并通过更改这些行为并重新分析效果来进行实验。

  • 避免在渲染时创建新的 GL 资源。这意味着优化在渲染时调用 glGen*()glCreate*() 函数(glGenTextures()glGenBuffers()glCreateShader() 等)。如果需要新的资源,请尝试在尝试使用它们进行渲染之前的一两帧创建并上传它们。

  • 同样,不要删除任何刚刚用它渲染过的 GL 资源。函数 glDelete*() 如果驱动程序检测到任何资源正在使用,则可能会引入完整的管道刷新。最好只在加载时删除资源。

  • 永远不要在渲染时调用 glGetError()glCheckFramebufferStatus()。这些函数应仅限于在加载时检查,因为这两者都可能进行完整的管道同步。

  • 类似地,不要在渲染时调用任何 glGet*() API 函数,而是在启动和加载时查询它们,并在渲染时参考缓存的结果。

  • 尝试避免在渲染时编译着色器,glCompileShader()glLinkProgram() 都可能非常慢。

  • 不要调用 glReadPixels() 在渲染时将纹理内容复制回主内存。如有必要,请改用 WebGL 2 GL_PIXEL_PACK_BUFFER 绑定目标,先将 GPU 表面复制到离屏目标,然后再将该表面的内容复制回主内存。

GPU 驱动程序友好的内存访问行为

在 CPU 和 GPU 之间传输内存是 GL 性能问题的常见来源。这是因为创建新的 GL 资源可能很慢,并且如果数据尚未准备好,或者如果在能够使用新版本覆盖旧版本数据之前仍然需要旧版本数据,则上传或下载数据可能会阻塞 CPU。

  • 与包含平面属性的多个 VBO 相比,更喜欢将交错的顶点数据保存在单个 VBO 中。这改善了 GPU 顶点缓存行为,并避免了在设置用于渲染的顶点属性指针时进行多次冗余的 glBindBuffer() 调用。

  • 避免调用 glBufferData()glTexImage2D/3D() 在运行时调整缓冲区或纹理内容的大小。在增加或减小动态 VBO 大小时,使用 std::vector 样式的几何数组增长语义来避免每帧都必须调整大小。

  • 更新缓冲区纹理数据时,更喜欢调用 glBufferSubData()glTexSubImage2D/3D(),即使纹理或缓冲区的整个内容发生更改也是如此。如果缓冲区的大小会缩小,请不要急于重新创建存储,只需忽略多余的大小。

  • 对于动态顶点缓冲区数据,考虑每帧双缓冲甚至三缓冲 VBO,以避免上传仍在使用的 VBO。更喜欢使用 GL_DYNAMIC 顶点缓冲区而不是 GL_STREAM

当 GPU 是瓶颈时

在验证 CPU-GPU 管道同步气泡没有发生并且渲染仍然受 GPU 限制之后,以下优化可能会有用。

  • 在正向照明渲染器中,几何体的多个加性照明绘制过程可能很容易实现,但是由此产生的 GL API 调用次数可能过于昂贵。在这种情况下,考虑在一个着色器通道中计算多个灯光贡献,即使这会在一些对象不受某些灯光影响时在着色器中创建无操作算术运算。

  • 在足够的情况下(lowp)使用最低的片段着色器精度。在离线创作时积极地优化着色器,不要指望 GPU GLSL 驱动程序会动态地进行任何优化。这对移动 GPU 驱动程序尤其重要。

  • 首先根据目标 FBO 对渲染对象进行排序,然后根据着色器程序进行排序,第三,根据程序是 CPU 绑定还是 GPU 绑定,最小化任何其他必要的 GL 状态更改或最小化过度绘制。 这有助于基于平铺的渲染器。 当不再需要 FBO 的内容时,调用 WebGL 2 glDiscardFramebuffer()

  • 使用 GPU 分析器或实现自定义片段着色器,这些着色器可以帮助分析渲染场景的过度绘制程度。 大量的过度绘制不仅会产生额外的工作量,而且渲染到同一显示内存块的顺序依赖关系会减慢并行渲染速度。 如果在启用深度缓冲的情况下渲染 3D 场景,请考虑将场景从前到后排序以最小化过度绘制和冗余的每像素填充带宽。 如果在 3D 场景中使用非常复杂的片段着色器,请考虑执行深度预处理以将实际光栅化的颜色片段数量减少到绝对最小值。

优化加载时间和其他最佳实践

最后,一些杂项优化已被证明是有效的。

  • 在网络上,通常无法预期哪些压缩纹理格式可用。 将纹理编写到多个压缩纹理包中,例如每个格式一个,并在运行时下载相应的纹理以最大程度地减少过度下载。 将纹理和其他资产存储到 IndexedDB 以避免在后续运行中重新下载。 Emscripten 链接器标志 -sGL_PREINITIALIZED_CONTEXT 可以帮助编写一个 html 外壳页面,该页面在最前面执行此类纹理格式检查。

  • 考虑在下载其他资产时并行编译着色器。 这有助于隐藏缓慢的着色器编译时间。

  • 在页面加载过程的早期测试用户浏览器是否支持 WebGL,然后再下载大量资产。 用户可能需要等待下载几兆字节的资产,然后才收到一条错误消息,说明 WebGL 在等待后不可用,这会让用户感到沮丧。

  • 如果 WebGL 初始化失败,请使用 "webglcontextcreationerror" 回调检查 WebGL 上下文错误原因。 浏览器可以在上下文创建错误处理程序中提供良好的诊断信息,以帮助诊断根本原因。

  • 密切关注画布的可见大小(DOM 元素的 CSS 像素大小)与画布上初始化的 WebGL 上下文的物理渲染目标大小,并确保这两个大小匹配以渲染 1:1 像素完美的 内容。

  • 使用 failIfMajorPerformanceCaveat 标志探测上下文创建,以检测何时在软件上进行渲染,并在这种情况下减少图形保真度。

  • 确保只使用最少数量的必要功能来初始化 WebGL 上下文。 WebGL 上下文创建参数 包括对 alpha、深度、模板和 MSAA 的支持,并且大多数情况下例如对 alpha 混合画布以适应 HTML 页面背景的支持是不需要的,并且应该被禁用。

  • 避免使用任何 *glGetProcAddress() API 函数。 Emscripten 为所有 GL API 函数提供静态链接,即使是所有 WebGL 扩展。 *glGetProcAddress() API 仅出于兼容性原因提供,以简化现有代码的移植,但通过调用动态获取的函数指针访问 WebGL 明显比直接函数调用慢,因为动态分派必须在 asm.js/WebAssembly 中执行额外的函数指针安全性验证。 由于 Emscripten 静态链接了所有 GL 入口点,因此建议利用它以获得最佳性能。

  • 始终使用 requestAnimationFrame() 循环来渲染动画,而不是 setTimeout() API。 这在动画滴答声上提供了最流畅的调度。

迁移到 WebGL 2

与 WebGL 1 相比,新的 WebGL 2 API 提供了实质上免费的 API 优化,这些优化只需针对 WebGL 2 即可激活。 这项提速来自于 WebGL 2 API 是从 JavaScript 绑定的角度进行修改的,现在可以使用 WebGL 而不必分配会导致 JS 垃圾收集器压力的临时对象。 这些新的入口点与 asm.js 和 WebAssembly 应用程序更匹配,并使 WebGL API 的使用更加简化。 作为案例研究,将虚幻引擎 4 更新为目标 WebGL 2,而无需其他引擎修改,可使吞吐量性能提高 7%。

由于这种免费性能的来源,强烈建议所有开发人员迁移到目标 WebGL 2,即使不需要其他 WebGL 2 功能,如果性能是一个问题。 WebGL 2 从 Firefox 51 和 Chrome 58 开始可用(参见 #4945)。 另请参见 caniuse: WebGL 2 表格。 只要稍微注意一下,就可以同时针对 WebGL 1 和 WebGL 2 API,并在可用时利用最佳性能,但在兼容性较低的 GPU 上优雅地回退。

在使用这两个规范时,请记住 WebGL 1 基于 OpenGL ES 2.0 规范,而 WebGL 2 基于 OpenGL ES 3.0 规范.

由于 WebGL 与 OpenGL ES 一样,不是一个向后兼容的 API,因此迁移到 WebGL 2 有点复杂。 也就是说,WebGL 1/OpenGL ES 2 应用程序通常不会仅仅通过初始化 GL 上下文的更新版本来运行在 WebGL 2/OpenGL ES 3.0 上。 这是因为这两个版本之间引入了一些向后兼容性中断更改。 但是,这些更改更多是表面上的/化妆性的,而不是功能性的,并且从功能上讲,WebGL2/OpenGL ES 3.0 包含 WebGL 1/OpenGL ES 2 中存在的所有功能。 只是调用不同 API 函数的方式发生了变化。

要从 WebGL 1 迁移到 WebGL 2,请注意以下已知的向后不兼容性列表。

  • 在 WebGL 2 中,一些 WebGL 1.0 扩展已合并到核心 WebGL 2 API 中,并且在查询不同 WebGL 扩展列表时不再宣传这些扩展存在。 例如,WebGL 1 中实例渲染的存在由 ANGLE_instanced_arrays 扩展提供,但这是 WebGL 2 的核心功能,因此不再在 GL 扩展列表中报告。 如果在应用程序中同时针对 WebGL 1 和 WebGL 2,请记住在检测功能是否存在时检查扩展和核心上下文版本号。

  • 上述的一个副作用是,当功能合并到核心时,调用该功能的特定函数名称发生了变化,即在 WebGL1/GLES 2 上下文中,将调用函数 glDrawBuffersEXT(),但对于 WebGL2/GLES 3.0,应该调用无后缀的函数 glDrawBuffers()

  • 已采用到核心 WebGL 2 规范的 WebGL 1 扩展的完整列表是

    ANGLE_instanced_arrays
    EXT_blend_minmax
    EXT_color_buffer_half_float
    EXT_frag_depth
    EXT_sRGB
    EXT_shader_texture_lod
    OES_element_index_uint
    OES_standard_derivatives
    OES_texture_float
    OES_texture_half_float
    OES_texture_half_float_linear
    OES_vertex_array_object
    WEBGL_color_buffer_float
    WEBGL_depth_texture
    WEBGL_draw_buffers
    

这些扩展在没有任何功能更改的情况下被采用,因此在初始化 WebGL2/GLES 3.0 上下文时,这些扩展可以直接使用,而无需检查扩展是否存在。

  • 一个值得注意的补充是,WebGL 2 引入了一种新的 GLSL 着色器语言格式。 在 WebGL 1 中,人们使用 OpenGL ES 着色语言,版本 1.00 在着色器代码中使用 #version 100 版本预处理指令来编写着色器。 WebGL 2 引入了新的着色器语言版本,OpenGL ES 着色语言,版本 3.00,它由着色器代码中的预处理指令 #version 300 es 标识。

  • 在 WebGL 2/GLES 3.0 中,可以继续使用 WebGL 1/GLES 2 #version 100 着色器,或者迁移到使用 WebGL 2/GLES 3.0 #version 300 es 着色器。 但是请注意,WebGL 2 存在一个向后不兼容性,即 WebGL 扩展 OES_standard_derivativesEXT_shader_texture_lod#version 100 着色器中不再可用,因为这些功能不再作为扩展存在。 使用这些扩展的 #version 100 着色器必须改写为 #version 300 es 格式。 Emscripten 提供了一个链接器标志 -sWEBGL2_BACKWARDS_COMPATIBILITY_EMULATION,它执行基于字符串搜索替换的自动迁移 #version 100 着色器到 #version 300 es 格式,当检测到其中任何一个扩展时,尝试隐藏这种向后兼容性中断。

  • 在 WebGL 2/GLES 3.0 中,一些纹理格式枚举已更改为扩展引入的纹理格式。 现在不再可能使用来自 WebGL 1/GLES 2 扩展的所谓“无大小”纹理格式,而是必须使用格式的新大小变体作为 internalFormat 字段。 例如,不是使用 format=GL_DEPTH_COMPONENT, type=GL_UNSIGNED_INT, internalFormat=GL_DEPTH_COMPONENT 创建纹理,而是需要在 internalFormat 字段中指定大小,即 format=GL_DEPTH_COMPONENT, type=GL_UNSIGNED_INT, internalFormat=GL_DEPTH_COMPONENT24

  • WebGL 2/GLES 3.0 纹理格式的一个特殊问题是,当 WebGL 1/GLES 2 扩展 OES_texture_half_float 被纳入到核心 WebGL 2/GLES 3.0 规范中时,半浮点(float16)纹理类型的枚举值发生了变化。 在 WebGL1/GLES 2 中,半浮点由值 GL_HALF_FLOAT_OES=0x8d61 表示,但在 WebGL2/GLES 3.0 中,枚举值 GL_HALF_FLOAT=0x140b 被使用,这与其他纹理类型扩展形成对比,在其他纹理类型扩展中,包含到核心规范中通常保留了使用的枚举的值。

总的来说,为了简化同时针对 WebGL1/GLES 2 和 WebGL2/GLES 3.0 上下文,Emscripten 提供了一个链接器标志 -sWEBGL2_BACKWARDS_COMPATIBILITY_EMULATION,它隐藏了上述差异,这得益于自动检测的迁移,从而使现有的 WebGL 1 内容能够透明地也针对 WebGL 2,以获得它提供的免费加速。

如果您在此模拟中发现缺失的项目,或者有任何改进本指南的意见,请将反馈提交到 Emscripten 错误跟踪器.