【问题标题】:Questions about Kinetic Layers关于动力学层的问题
【发布时间】:2014-03-28 17:29:04
【问题描述】:

动力学层是如何合成的?如题,具体实现是什么?

起初我认为每一层都是它自己的画布(文档暗示了这一点),但除非这些是屏幕外的画布元素,否则情况似乎并非如此。如果它们不在屏幕上,我仍然会受到违反直觉的性能影响。

我有一个使用 Kinetic 的组件,它有 3 层。称它们为 backgroundLayer、activeLayer 和 selectionLayer。第一个在加载新文档时呈现一次;第二个很少更新,但有可能需要移动或添加/删除的元素;最后一个只有一个非常小的元素。

令我惊讶的是,如果我有一个 20fps 的循环渲染,其中我明确检查每一层是否有我自己的“脏标志”,并且只在它脏时渲染该层,即使我只更新 selectionLayer,帧率也会爬行,即使该图层已将 clearBeforeDraw 设置为 false。我可以确认此属性正在阻止清除,因为移动选择会留下像素痕迹。

舞台通常是 600x2000,但 selectionLayer 中的单个元素是大约 20x100 的 Rect。

我怀疑正在发生的事情是每个图层都呈现为屏幕外画布,但随后这些画布被合成到单个可见画布中。可能我的 selectionLayer 可以快速更新屏幕外(我可以添加清除旧矩形以保持快速),但是在合成图层时它有效地 blitting 600x2000 透明像素(或者更糟糕的是,导致 2 个图层没有已更新为也可以组合在一起)。

这对正在发生的事情准确吗?如果是这样,我会采取不同的方法来保持快速渲染吗?我正在考虑只拥有一个单独的 Kinetic.Stage (因此也是画布),但这是一种解决方法,它开始失去图层的一些明显好处。如果层仅用于组织代码但具有这种性能影响,我认为这应该记录在案。每层也有屏幕上的画布元素会很好,尽管我意识到这将是对库的重大改变。但除此之外,我似乎需要在 DOM/CSS 级别做额外的工作来协调我的图层并获得我需要的性能目标。

重新编辑以包含示例代码并将问题归结为本质:

在组件的 init() 中:

stage = new Kinetic.Stage({
    container: containerID,
    width: CANVAS_WIDTH, // in test case, 1320
    height: CANVAS_HEIGHT// in test case, 8712
});
backgroundLayer = new Kinetic.Layer({
    hitGraphEnabled: false,
    clearBeforeDraw: true // i like explicit
});
backgroundLayer.listening(false); // is this redundant to hitGraphEnabled?
activeLayer = new Kinetic.Layer({
    hitGraphEnabled: false, 
    clearBeforeDraw: true
});
activeLayer.listening(false);
playheadLayer = new Kinetic.Layer({
    hitGraphEnabled: false,
    clearBeforeDraw: true
});
playheadLayer.listening(false);
playhead = new Kinetic.Rect({
    fill: 'red', 
    opacity: 0.3, 
    stroke: 'black',
    x: 0, y: 0,
    width: 10, 
    height: ROW_HEIGHT // 100
});
// playhead.transformsEnabled(false); // can't do this, or setX/Y() do nothing
playheadLayer.add(playhead);
stage.add(backgroundLayer);
stage.add(activeLayer);
stage.add(playheadLayer);

在组件的 render() 中:

function render(MSSinceRuntime) {
    animationFrameID = reqAnimFrame(render); // umm...
    if (MSSinceRuntime - lastDrawTimeMSSinceRuntime < clamp) {
        return;
    }
    lastDrawTimeMSSinceRuntime = MSSinceRuntime;

    if (backgroundLayer.dirty) {backgroundLayer.drawScene(); }
    if (activeLayer.dirty) { activeLayer.drawScene(); }
    if (playheadLayer.dirty) { playheadLayer.drawScene(); }

    backgroundLayer.dirty = activeLayer.dirty = playheadLayer.dirty = false;
}

最后:

var lastMeasureY = 0;
function playheadUpdated(event) {
    var timecode = event.beat;
    var measureY = getMeasureY(timecode.measure);
    playhead.setX(getTimecodeX(timecode));
    playhead.setY(measureY);
    playheadLayer.dirty = true;
    lastMeasureY = measureY;
}

请注意,只有 playheadLayer 设置为脏,并且我已确认仅渲染此层。在 Chrome 的分析器火焰图中,我可以看到每次对 render() 的调用需要约 100 毫秒,其中约 99 毫秒将是 Kinetic.Context.drawImage() 调用 [context2d.] drawImage() 作为堆栈上的最终调用。

调用是做什么的,为什么?

目前我并不关心我可能想做的各种其他优化,包括使用单独的画布或将我的怪物 UI 组件分割成更易于缓存的画布元素。我试图理解这个问题,因为它会影响我接下来选择优化的内容。也就是说,所有其他优化建议都值得赞赏。

【问题讨论】:

  • 附带问题:我没有在文档中遇到过这个问题,但我认为我不能拥有与舞台不同大小的图层,对吧?如果可以的话,我的 selectionLayer 将只有 20x100,这可能会允许更快地呈现原样或对库的合成代码进行非常微不足道的更改。
  • 正确,所有画布都固定在与舞台相同的大小;-)

标签: javascript performance canvas kineticjs


【解决方案1】:

关于 KineticJS 层

每个 KineticJS 层实际上是 2 个画布。一个画布用于可见显示,另一个画布用于屏幕外工作,如点击测试和拖动操作。所以你的 3 层实际上是 3 组画布(总共 6 幅画布)。

背景层

如果你的背景层从不改变,告诉它不要监听事件。事件系统使用大量资源,因此这应该可以为其他任务腾出 cpu 时间。您甚至可以考虑将背景放在使用 CSS 定位在 Kinetic 容器下的图像元素上。这样一来,KineticJS 就根本不需要为其分配任何资源。

backgroundLayer.listening(false);

活动层

如果该层上的所有添加/删除/移动都以编程方式完成,并且您不需要用户单击/拖动活动层上的任何元素,那么也可以在该层上设置监听。

activeLayer.listening(false);

如果您仍需要监听活动层事件,但不需要命中测试,请使用 drawScene 代替 draw。 drawScene 命令仅绘制可见的画布,而不是屏幕外的命中画布。

activeLayer.drawScene();

令人惊讶的是,您可能会发现让 KineticJS 清除/重绘整个 activeLayer 比使用 clearBeforeDraw 进行微观管理更快。这是因为 GPU 可以更快地清除整个画布,而不是清除画布的一部分。画布有一个内部像素颜色数组,它很快就会被零填充以擦除整个画布。要清除画布的一部分,浏览器必须计算起始像素位置并跟踪它,因为它仅对像素数组的一部分进行零填充。

当更新活动层上的多个节点时,使用 batchDraw(),它使用 window.requestAnimationFrame “在幕后”。皇家空军自身与显示硬件集成,因此减少了绘制被显示刷新中断的可能性。

activeLayer.batchDraw();

选择层

如果您在选择层上的节点没有被旋转或缩放,您可以禁用此节点的转换以大大提高性能。

myNode.transformsEnabled(“none”);

如果选择层上的节点还不是图像,请考虑将其缓存到图像中。图像主要由 GPU 进行 blitted,而重绘形状也需要 CPU 付出相当大的努力。

myNode.cache({…});

其他性能方面的东西

…而且,n-e-v-e-r 使用阴影。阴影需要 KineticJS 和本机 html 画布进行特殊昂贵的处理。如果您需要阴影节点:

  • 绘制节点。
  • 应用阴影。
  • 将节点缓存为图像(包括阴影)

如果您要管理自己的动画,请使用 requestAnimationFrame 而不是 setInterval/setTimeout。 RAF 通过批处理待处理的命令并将其绘制与显示的刷新周期协调来最大限度地提高性能。

一些有用的信息

阅读 KineticJS 的更改日志。它包含有关性能相关问题的有用信息:

https://github.com/ericdrowell/KineticJS/wiki/Change-Log

希望这对您的项目有所帮助并祝您好运!

【讨论】:

  • 我很感激所有的信息和提示——我实际上已经在做所有这些了。我应该将您的答案标记为回答我的问题,但不幸的是,答案是我的问题是由于 Kinetic 造成的,并且正如我所描述的那样。当由于透明 blit 的大小而移动该对象时,具有一个微小对象的图层会导致巨大的渲染命中。这让我质疑拥有屏幕外画布的价值,而不是仅在屏幕上使用这些画布,这对于图层来说会快得多。我可以通过编辑我的原始帖子来详细说明...
  • 澄清并提出一个相关问题:我相信 blitting 没有得到任何 GPU 加速,因为 a) 画布的大小太大,所以如果是,肯定会发生一些抖动, b) 我从未读过 Kinetic 尝试自动使用 3D 上下文(如果可用),并且 c) 我的目标之一是原生 iOS 应用程序,而 UIWebView 不提供 WebGL。我在其中一些假设中是不正确的吗? Kinetic 是否尝试使用 3D 上下文?无论 OpenGL ES 如何,HTML5 画布都有一些 GPU 加速吗?如果是的话,我应该把我的组件切成小块吗?
  • KineticJS 仅使用 2d 上下文。如果您的 IOS 画布尺寸为 1320x8700 甚至 600x2000 且带有多个画布,那么您的性能问题是由于画布太大而无法移动设备处理(甚至可能太大而无法桌面处理)。您可能必须重构代码以使用程序逻辑而不是大画布。祝你的项目好运!
  • 我相信你在回避这个问题。这一切都在我的 iPad 2 上运行良好,直到我做了我的问题中描述的事情,即在一层中移动单个小对象。我同意我需要重构(该层,使用单独的画布)。但我仍然要指出,使用 blitted 到屏幕画布上的屏幕外画布似乎没有性能优势,并且会导致我遇到的确切问题。
  • 这里没有踢脚线!如果您认为第二个屏幕外画布没有任何好处,那么您可以使用上述技术仅使用场景画布并避免使用屏幕外命中画布。如果您认为 KineticJS 对您的任何一个层有太多开销,您可以使用实际的 html 画布或 CSS 代替该层。如果您认为 KinetcJS 需要更改,那么这里有一个发送拉取请求的链接:github.com/ericdrowell/KineticJS/pulls
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-12-23
  • 1970-01-01
  • 2011-01-12
  • 2018-01-08
相关资源
最近更新 更多