【问题标题】:WebGL, optimizing geometry calculation on web workerWebGL,优化网络工作者的几何计算
【发布时间】:2014-01-24 17:24:35
【问题描述】:

嘿,我正在开发一个 webgl 应用程序,它需要动态生成大量 2d 几何图形,例如折线、复杂的多边形。目前使用的顶点/颜色/索引/纹理坐标是预先分配的,然后根据主更新循环中的用户输入进行计算。

我尽量回收利用,例如。绘制 150000 个圆圈时,我尝试确保仅计算一次几何/颜色/texcoords/索引,除非基本参数(圆圈细节,...)不改变,然后平移、缩放(x&y)、旋转此“圆形蓝图”的顶点(移动索引并设置顶点颜色)x 倍于用户指定的数量。
之后通过一个 drawArrays/drawElements 调用发送数据。绘制折线和其他图元的处理方式类似。

虽然这已经相当快了,但显然很多时间都花在了将原始顶点转换为新的位置和尺寸上,有时只留下少量资源供用户用于实际构建要显示的东西。
所以我问自己我怎样才能外包那些“简单”的计算。在考虑以 gpgpu 方式进行之前,我曾考虑将这些计算转移到网络工作者身上。

这就是我的想法:

  1. 主线程:用户发送请求绘制 150000 个圆,提供尺寸、变换和颜色
  2. 主线程:请求被推送到绘制请求队列
  3. 主线程:请求数据被发送到 web worker(如果 worker 尚未启动或完成之前的队列项)
  4. 网络工作者:正在使用足够大的预分配数据集来构建几何并对其进行转换
  5. web worker:传输转换后的 Float32Array 顶点、颜色数据和 Uint16Array 索引数据通过 可转移对象到主线程
  6. 主线程:onWorkeRequestProcessed:获取worker发送的数据,发送到glDrawArrays/glDrawElements调用, 将数据发回给工作人员
  7. 网络工作者:接收回数据,将“好的,一切都恢复原状,我准备好接受另一个请求”消息发送到主线程
  8. 主线程:移动队列,从前面发送下一个请求

... 所以最后希望有更多资源可用于主线程来执行一些用户发起的计算。

这样做意味着主循环总是必须等到工作人员完全处理完队列线性后才能以正确的顺序绘制所有内容,这最终可能会使使用工作人员的优势过时。

这是我目前所拥有的:https://gist.github.com/automat/8566773(没有 webgl 部分,只有消息队列,没有主循环,因为同步 framestep 和等待 worker 也是一个问题)

顺便说一句:通过纹理四边形假实例化几何实体不是一种选择。

你可以看看这里的项目:https://github.com/automat/canvasGL.js(src/gl/cglContext.js 应该特别有趣)

这有什么好处吗?任何提示表示赞赏...

【问题讨论】:

    标签: javascript optimization webgl web-worker


    【解决方案1】:

    在那个年代,我从事图形管道的工作,当时您的平均多边形咀嚼器是一组价值约 1.000.000 美元的 VME 机架,并且每秒几乎无法吐出 10K 多边形。接下来是我脑海中一个非常粗略和粗略的轮廓,但我已经编写了使它工作的软件,我很确定这种方式确实可以以相当有效的方式完成工作。

    基本上你需要的是一个好的数据依赖分析。

    如果您想获得一些计算能力,您必须在尽可能多的 CPU 内核上生成工作线程。您的问题是简单并行化的完美案例,因为您要计算的每个几何图元之间没有依赖关系。

    在这里,您的主线程似乎无事可做,除了旋转它的虚拟拇指等待您的工人完成他的圈子咀嚼业务。您将避免阻塞浏览器用户界面,但在 CPU 能力方面一无所获。

    但是,您可以执行以下操作:

    • 将您的圈子分成相当大的数据包(假设每个圈 100 个,总共 100 个圈子 150 个数据包)。
      将所有这些数据包放入一个公共输入队列中
    • 在每个 CPU 上运行相同工作线程的实例
    • 每个工作线程将从公共输入队列中挑选下一个可用数据包,对其进行处理并将结果放回公共输出队列。
    • 让主线程从输出队列中抓取数据包并将它们提供给饥饿的 GPU。

    使用这种架构,您的所有 CPU 将全速运行,在圆上进行真正的并行计算。

    通用队列将充当简单的自动负载平衡器。
    想象一下,出于某种原因,某些圆圈的计算成本比其他圆圈高。不幸的 CPU 能够处理数据包,只会花更多时间处理它,而它的兄弟会从队列中弹出更多更快的数据包。
    这就像在普通邮局的办公桌上发生的工作负载平衡一样,当多个办公桌为一个客户队列提供服务时。

    在处理完第一批 100 个圆圈后,从您的高级软件到 GPU 的管道将开始填满。运行您的主应用程序的 CPU 可能会花费很少的时间来为 GPU 提供数据,并且工作线程将有将近 100% 的 CPU 时间来执行与其在专用 CPU 上运行的兄弟姐妹相同的计算。

    总计算时间几乎除以系统上可用的 CPU 数量(在同步和其他背景噪音中损失 5%)。

    整个管道的全局同步需要一些思考,但基本上你知道为给定帧生成了多少数据包,因此你可以轻松计算通过输入和输出队列的数据包数量,从而确定给定的图像生成周期何时结束。

    但请注意:这假设您的圆圈数据包确实可以独立计算,即它们仅依赖于当前帧的全局信息(通常是相机位置和一些照明参数)。
    如果您必须执行全局遮挡剔除等操作,您可以告别并行处理(当然,除非您为此设计了专门的可并行化算法)。

    编辑:

    我进行了一些基本测试,看看工作人员是否充分利用了其他 CPU。请参阅this fiddle,其中 9 或 10 个工作人员不断地在一个紧密的循环中运行。

    这在 FireFox 上进行了测试,但由于使用 blob 创建工作代码的安全限制,未能在 IE11 上运行。我只使用这个 hack 来将演示放入 JSFiddle。如果您使用常规的 .js 文件,我想代码应该在 IE11 上运行良好。

    <script id='worker' type='worker'>
    onmessage = function (e)
    {
        // eat 100% CPU for the requested number of seconds
        var done = Date.now()+e.data*1000;
        while (Date.now() < done) { /* wasting CPU here */ }
    
        // signal done
        postMessage('');
    };
    </script>
    

    由于某种原因,CPU 似乎没有被完全占用。这可能与我的虚拟等待循环有关,或者可能是工作分配没有充分利用多核。
    根据我从 Mozilla 文档中了解到的情况,worker 应该在多个内核上生成,但据我所知,你无法控制它。

    无论如何,如果您使用真实计算而不是虚拟计算,这可能是某些性能测试的基础。当然,您应该使用直接数据访问,否则消息副本将破坏在工作人员之间共享数据的大部分好处。

    【讨论】:

    • 这并不完全正确。将工作推送给工作人员可使主页保持响应。如果计算某些几何图形需要 500 到 10000 毫秒,如果您在主页上执行此操作,除非您编写一些渐进式事件驱动生成器,否则网页会冻结 500 到 10000 毫秒。用户不能做任何事情。另一方面,如果您将该工作推送给工作人员,则页面可以继续响应用户输入。是否有 2 个 CPU 甚至都没有关系,因为工作线程是线程,这仍然意味着您的主页将有机会响应用户输入。
    • @gman ???用户界面与这一切有什么关系?显然,CPU 密集型工作将以一种或另一种方式在其他线程中完成。用户界面线程唯一需要做的就是控制整个计算链:启动/停止它并传递用户输入(相机移动、玩家位移等)。
    • 我们在谈论 WebGL,所以我们在浏览器中。让我们留在屏幕上的半径滑块。如果生成一个球体需要 10000 毫秒(10 秒),那么在整个 10 秒内,滑块都会变得无响应。另一方面,如果在工作人员上计算球体,则滑块保持响应。可能需要 10 秒才能显示正确大小的球体,至少滑块没有卡住 10 秒。用户还可以更新其他内容、文本字段、其他滑块等。但前提是几何是在工作人员中计算或分解为小事件。
    • 重点是即使在工作人员中生成几何图形的总时间并不快,仍然有理由将缓慢的操作转移到工作人员身上。从主页的角度来看,浏览器运行单线程。如果 JavaScript 在硬循环中运行,则页面被冻结。您需要每隔几毫秒将控制权交还给浏览器,否则它无法响应页面中的用户输入。这是一个示例jsfiddle.net/greggman/r26mx
    • 有趣的讨论。我会试一试,将任务分配给几个网络工作者。
    猜你喜欢
    • 2014-01-14
    • 2012-04-03
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-03-16
    相关资源
    最近更新 更多