在屏幕外进行3D渲染。使用画布工作者获得最大性能
8月6日- 9分钟阅读
OffscreenCanvas Web API使我们能够将Canvas DOM元素的所有权转移到worker中。
由于工作者在独立的线程中运行(如果可能的话,使用自己的 CPU),这意味着昂贵的应用程序相关的 JS 逻辑不会降低 Canvas 的渲染性能,反之亦然:Canvas 的昂贵逻辑不会影响应用程序的其他部分。
良好的用例是图像处理以及渲染互动图表或地图。由于 Canvas 也支持 WebGL,因此使用屏外概念创建一个游戏引擎也是有意义的。
内容
- 简介
- 浏览器支持
- 框架相关的增强功能
- 夸奖
- 创建基于neo.mjs的演示应用程序
- 创建自定义的WebGlComponent
- 研究canvas.Helper类
- 在浏览器中直接使用d3
- 演示视频
- 在线演示
- 最后的思考
1.简介
工作者不能访问DOM →window 以及window.document 都没有定义。
在没有这个API的情况下,实现多线程是有点困难的。neo.mjs项目以一种好的方式解决了这个问题。
使用一个应用工作者作为主要角色,让主线程尽可能地闲置。虚拟DOM是实现这一目标的必要条件,因为你的应用程序(包括你的组件)生活在App worker里面。
为了最大限度地利用OffscreenCanvas Web API,我们需要以下列方式增强工作者的设置。
"结合离屏画布和应用程序工作者 "也是本文的合适标题,但我们还将介绍在此设置中创建一个漂亮的基于WebGL的演示应用程序。
2.浏览器支持
Chromium(Chrome,Edge)已经很好地支持新的API。
虽然兼容性表一开始看起来很吓人。
重要的是要知道,Mozilla(火狐)和Webkit(Safari)团队正在积极推动这个话题。
[
1390089 - (offscreen-canvas) [meta] OffscreenCanvas
Core中的新(无人)--Canvas。WebGL。最后更新于2021-07-29。
bugzilla.mozilla.org
](bugzilla.mozilla.org/show_bug.cg…)
估计。"最好是在今年"
[
183720 - 完成OffscreenCanvas的实现
Bug 183720:完成OffscreenCanvas的实现
bugs.webkit.org
](bugs.webkit.org/show_bug.cg…)
"现在bug 224178已经落地,我相信所有主要的OffscreenCanvas功能都已实现,并在Linux平台上启用。还有相当多的工作要做,以巩固和优化这个实现,当然还有在所有平台上启用它,但基础是存在的。好哇!"
我们将需要时间来创造惊人的实现,所以现在开始是有意义的。
3.框架相关的增强
我们首先需要的是新的画布工作者:
src/worker/Canvas.mjs
这里的关键部分是使用afterConnect() 方法来创建一个新的MessageChannel ,它在App和Canvas工作者之间建立了直接连接(postMessages不需要通过主线程传递)。
我们将Canvas节点存储在一个map 配置中,使用
DOM(===组件id)作为关键。
我们还需要创建一个新的组件类:
src/component/Canvas.mjs
我们使用的是offscreen 配置,默认为true 。
在一个新的canvas组件被安装后,afterSetMounted() 方法将被触发。如果offscreen 被设置为 "true",它将请求画布节点的所有权,并将其传递给画布工作者(它将把它存储在刚刚提到的地图中。
之后,offscreenRegistered 配置将被设置为真,这样我们就可以使用afterSetOffscreenRegistered() 作为我们自己的组件实现的入口。
任务完成了。
4.嘉奖
由于我不想涵盖所有关于如何使用WebGL的逻辑(偏离主题),我一直在寻找一个很好的、有记录的演示。
我很幸运。
在此向Chris Pierce致敬,感谢他的出色作品
[
用OffscreenCanvas渲染图表
对于浏览器来说,渲染图表是一个非常密集的操作,尤其是在上下文中的多个图表小部件...
blog.scottlogic.com
](blog.scottlogic.com/2020/03/19/…)
虽然这篇文章已经是一年前的了,但它在很多方面仍然是相关的。
我强烈反对的一点是使用多个MessageChannels来覆盖不同的消息类别。在postMessages的基础上使用自己的API才是正确的做法,并在neo.mjs项目中实现。
你可以在这里找到Chris的演示版本。
[
GitHub - chrisprice/offscreen-canvas。使用offscreen canvas渲染图表的例子
对于浏览器来说,渲染图表是一个非常密集的操作,尤其是在上下文中的多个图表小工具...
github.com
5.创建基于neo.mjs的演示应用程序
我从使用CLI开始,使用:
,生成了一个新的工作区。npx neo-app
你可以在这里找到最终的结果。
[
GitHub - neomjs/offscreen-canvas。使用 worker.Canvas 的演示应用程序
使用worker.Canvas的演示应用程序。通过在GitHub上创建一个账户,为neomjs/offscreen-canvas的开发做出贡献。
github.com
由于新的画布工作者是可选的,我们需要做的第一件事是在我们的neo-config.json 文件中激活它。
该框架使用app.mjs 文件作为您的应用程序工作者的入口点。
因此,使用一个名为canvas.mjs 的文件作为新的画布工作者的起点是有意义的。
这个也是使用一个onStart() 方法,允许你在工作者准备好后触发逻辑。
让我们从MainContainer.mjs 视图开始。
我们使用的是一个具有垂直框(vbox)布局的视口。
由于我们使用的是d3,我们将自定义WebGlComponent 映射到一个容器中(自定义d3-fc 标签),该容器负责根据需要调整画布节点的大小。
我们在底部放了一个带有几个按钮的工具条。
你会注意到一些MyApp.canvas.Helper.* 方法被触发了。这是基于远程方法的访问(remotes API),我们将在后面更深入地介绍它。
onStopMainButtonClick() 将触发Neo.Main.alert() ,因为工作者不能自己调用alert() 。这个也是一个远程方法。显示警报将停止主线程相关的JS执行。然而,它并没有停止与UI相关的渲染线程,所以我们的canvas worker仍然可以继续为我们的canvas节点制作动画。
6.创建自定义的WebGlComponent
只需要几行代码就可以了。
再一次,d3要求我们创建一个自定义的包装节点
(d3fc-canvas tag)。
我们使用这个节点来应用自定义的d3measure domListener,所以我们可以将我们的vdom根保持在最高级别。
然而,我们仍然需要为我们的内部canvas节点提供一个唯一的ID,因为我们正在使用它来请求所有权的转移。我在component.Canvas ,实现了getCanvasId() 方法,所以我们可以直接指向我们的第一个子节点。
7.查看canvas.Helper类
希望你还记得,我们的canvas.mjs 入口点导入了canvas.Helper 。所以这个文件(单子)将在canvas worker里面运行。
这里最重要的一点是第55行:我们正在将我们的一些类方法名称添加到remote 配置中。
这将在目标工作者内部注册名称空间,然后允许我们在新范围内调用相同的名称空间和函数名称作为承诺。
如果你想知道什么。
MyApp.canvas.Helper.enableAnimation(enableAnimation);
里面的MainContainer.mjs文件,这就是它的答案。
在引擎盖下,它将从app worker向canvas worker发送一个postMessage ,触发相关的方法并发送一个包含返回值的回复postMessage 。
由于我们的方法是一个Promise,我们可以使用then() 作为回调,或者与async 和await 。
一旦d3和d3fc文件被导入,我们将遵循Chris Scott的演示应用程序的逻辑。生成数据、系列,一旦画布节点的所有权到达,我们就可以渲染系列。
与原来的演示不同的是,改变项目(点)的数量不需要重新加载整个应用程序(页面),但会动态地生成一个新的画布系列。我还添加了一个停止/重新启用动画的功能。
这很方便,工人有自己的requestAnimationFrame() 实现。我认为OffscreenCanvas是它的第一个也是唯一一个用例。
8.在浏览器中直接使用d3
这可能是我花了最多时间处理的项目。
看一下Chris的演示。
[旁注]d3-collection在这一点上已经被废弃了→不再需要了。
当使用基于JS模块的工作者时,使用importScripts() 是不可能/不允许的。
new Worker('App.mjs', {type: 'module'})
我尝试的第一件事是。
这在开发模式下运行得非常好(意味着直接在浏览器中运行,没有任何构建或转译)。
然而,我也想让它在dist/development 和dist/production 环境中运行,这些环境是基于 webpack 的。
使用静态导入会产生一个巨大的分割块,对我来说,这并不奏效(JS运行时间错误)。
为了解决分块的问题,我们需要切换到动态导入。
d3是相当刻薄的,因为每个文件基本上都是一个嵌套的函数,它将立即触发一个内部函数。每个内层函数都需要前面的导入已经存在了。
作为一个解决方案,我们确实需要动态导入,它可以作为一个序列被处理。这就是为什么我在我们的canvas.Helper文件中加入了以下逻辑。
[题外话]我主要坚持使用 "let "和逗号,而不是 "const",以实现更小的文件大小。
将每个导入文件包裹到一个函数中是超级重要的,例如:() => import() ,以确保导入文件不会被立即执行。然后,我们可以对每个单一的导入使用await 。肯定会慢一点,但我们在这里没有选择。
虽然d3能够将其函数添加到全局d3命名空间中,但对于d3fc库来说,实际上并不一样。在dist envs里面,它只是将函数添加到模块中,所以我们需要手动修复它。我将很快创建一个错误报告。
9.演示视频
这里有一个好东西是在最后。
我们正在将canvas worker Helper类记录到控制台。
我们可以直接改变这里的配置,用户界面会自动调整。
10.在线演示
请记住,这些演示目前只能在基于Chromium的浏览器中运行。
开发模式(按原样运行代码):
neomjs.github.io/pages2/work…
开发/生产(基于webpack):
neomjs.github.io/pages2/work…
我注意到,在开发模式下,画布动画的运行更加流畅,特别是在上升到100万点时。
但我并不100%确定原因。我的猜测是,webpack创建了封装函数来访问每个模块→总共有更多的函数调用,而这些调用加起来确实是很多。
点击 "stop main "按钮会创建一个alert(),暂停计时器,而我们的画布则继续制作动画。
11.最后的思考
如果你还没有找到时间深入研究neo.mjs项目,你真的应该。
[
GitHub - neomjs/neo: The application worker driven frontend framework
neo.mjs让你能够使用不止一个CPU来创建可扩展和高性能的应用程序。不需要照顾到每一个人。
淘宝网
新的canvas worker标志着v2.3版本的发布(我将很快调整版本号)。
完成日历将被推入v2.4版本(已经很接近了)。
我们现在已经有了基本的设置,可以在neo项目中创建一个图像、图表或游戏库。
如果有人想在这一部分做贡献,我们将非常感激
我期待着看到用新的OffscreenCanvas工作者构建的演示或实际应用
最好的问候和快乐的编码,
Tobias