我们很高兴宣布 Firefox 44+ 中的 Web Workers 中的 WebGL!使用新的 OffscreenCanvas API ,您现在可以创建脱离主线程的 WebGL 上下文。
要继续操作,您需要一个 Firefox 44 或更高版本的副本(目前是 Firefox 开发者版 或 Firefox Nightly)。您需要通过在 Firefox 中导航到 about:config,搜索 gfx.offscreencanvas.enabled
并将其设置为 true 来启用此 API。您可以从 GitHub 获取代码示例 或在 Firefox 44+ 中预览它 这里,将 gfx.offscreencanvas.enabled
设置为 true。此功能在 Windows 上尚不可用,等待 ANGLE 支持。 AlteredQualia 指出 这些功能在 Windows/FF Nightly 46 上运行良好。我应该为没有验证而感到羞愧!
用例
此 API 是第一个允许除主线程之外的线程更改显示给用户的内容的 API。这使得无论主线程正在进行什么操作,渲染都能继续进行。您可以在 工作组的正在进行的规范 中看到更多用例。
代码更改
让我们看一下 来自 我的 Raw WebGL 演讲 的 WebGL 动画基本示例。我们将移植此代码以在工作线程中运行,而不是在主线程中运行。
第一步是将所有代码从 WebGL 上下文创建移动到绘制调用到一个单独的文件中。
<script src="gl-matrix.js"></script>
<script>
// main thread
var canvas = document.getElementById('myCanvas');
...
gl.useProgram(program);
...
变成
// main thread
var canvas = document.getElementById('myCanvas');
if (!('transferControlToOffscreen' in canvas)) {
throw new Error('webgl in worker unsupported');
}
var offscreen = canvas.transferControlToOffscreen();
var worker = new Worker('worker.js');
worker.postMessage({ canvas: offscreen }, [offscreen]);
...
认识到我们正在调用 HTMLCanvasElement.prototype.transferControlToOffscreen,然后将其传输到新构造的工作线程。transferControlToOffscreen
返回一个新对象,该对象是 OffscreenCanvas 的实例,而不是 HTMLCanvasElement。虽然相似,但您无法访问诸如 offscreen.clientWidth
和 offscreen.clientHeight
之类的属性,但您可以访问 offscreen.width
和 offscreen.height
。通过将其作为第二个参数传递给 postMessage,我们将该变量的所有权转移到第二个线程。
现在在工作线程中,我们将等待接收来自主线程的带有画布元素的消息,然后再尝试获取 WebGL 上下文。获取 WebGL 上下文、创建和填充缓冲区、获取和设置属性和统一变量以及绘制的代码不会改变。
// worker thread
importScripts('gl-matrix.js');
onmessage = function (e) {
if (e.data.canvas) {
createContext(e.data.canvas);
}
};
function createContext (canvas) {
var gl = canvas.getContext('webgl');
...
OffScreenCanvas
只向 WebGLRenderingContext.prototype
添加了一个新方法,名为 commit。commit 方法将渲染的图像推送到创建用于 WebGL 上下文的 OffscreenCanvas
的画布元素。
动画同步
现在要使代码动画化,我们可以使用 postMessage 将主线程的 requestAnimationFrame 定时代理到工作线程。
// main thread
(function tick (t) {
worker.postMessage({ rAF: t });
requestAnimationFrame(tick);
})(performance.now());
和工作线程中的 onmessage 变成
// worker thread
onmessage = function (e) {
if (e.data.rAF && render) {
render(e.data.rAF);
} else if (e.data.canvas) {
createContext(e.data.canvas);
}
};
我们的渲染函数现在有一个最终的 gl.commit(); 语句,而不是设置另一个 requestAnimationFrame
循环。
// main thread
function render (dt) {
// update
...
// render
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
gl.drawArrays(gl.TRIANGLES, 0, n);
requestAnimationFrame(render);
};
变成
// worker thread
function render (dt) {
// update
...
// render
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
gl.drawArrays(gl.TRIANGLES, 0, n);
gl.commit(); // new for webgl in workers
};
这种方法的局限性
虽然在示例代码中,我没有进行正确的基于速度的动画(通过不使用从 requestAnimationFrame
传递的值,我正在进行帧速率依赖的动画,而不是更正确的基于速度的动画,它是帧速率独立的),但我们仍然在这个方法中遇到了问题。
假设我们将渲染逻辑移出了主线程,以避免 JavaScript 虚拟机 (JVM) 垃圾收集器 (GC) 暂停导致的暂停。主线程上的 GC 暂停会减慢 requestAnimationFrame
的调用速度。由于对 gl.drawArrays
和 gl.commit
的调用是由主线程上的 requestAnimationFrame
循环中的 postMessages 在工作线程中异步触发的,因此主线程上的 GC 暂停会阻塞工作线程上的渲染。注意:主线程上的 GC 暂停不应阻塞工作线程的进度(至少在 Firefox 的 SpiderMonkey 虚拟机中不会)。在 SpiderMonkey 中,GC 暂停是每个工作线程的。
虽然我们可以尝试在工作线程中做一些巧妙的事情来解决这个问题,但解决方案是使 requestAnimationFrame
在工作线程上下文中可用。跟踪此工作的错误可以在这里找到 这里。
总结
由于新的 OffscreenCanvas API,开发人员现在能够在不阻塞主线程的情况下渲染到屏幕上。获取工作线程上的 requestAnimationFrame
还有更多工作要做。我能够在几分钟内将现有的 WebGL 代码移植到工作线程中运行。作为比较,请参阅 animation.html 与 animation-worker.html 和 worker.js。
6 评论