javascript - 关于动力学层的问题

标签 javascript performance canvas kineticjs

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

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

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

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

舞台通常约为 600x2000,但选择层中的单个元素是大约 20x100 的矩形。

我怀疑正在发生的事情是,每一层都被渲染为屏幕外 Canvas ,但随后这些 Canvas 被合成到单个可见 Canvas 中。也许我的 SelectionLayer 可以快速更新离屏(并且我可以添加对旧矩形的清除以保持其快速),但是在合成图层时它会有效地位图传输 600x2000 透明像素(或者更糟糕的是,导致 2 个图层没有也已更新为合成在一起)。

这对正在发生的事情准确吗?如果是这样,我是否会采取不同的方法来保持渲染速度快?我正在考虑只拥有一个单独的 Kinetic.Stage (以及 Canvas ),但这是一种开始失去图层的一些明显好处的解决方法。如果层仅用于组织代码但具有性能影响,我认为应该对此进行记录。如果每层也有屏幕上的 Canvas 元素,那就太好了,尽管我意识到这对库来说是一个重大的改变。但除此之外,我似乎需要在 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() 作为堆栈上的最终调用。

该调用在做什么,为什么?

我目前并不关心我可能很想做的各种其他优化,包括使用单独的 Canvas 或将我的怪物 UI 组件切成更适合缓存的 Canvas 元素。我试图理解这个问题,因为它会影响我下一步选择优化的内容。也就是说,所有其他优化建议都受到赞赏。

最佳答案

关于 KineticJS 层

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

背景层

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

backgroundLayer.listening(false);

事件层

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

activeLayer.listening(false);

如果您仍然需要监听事件图层事件,但不需要 HitTest ,请使用drawScene而不是draw。 drawScene 命令仅绘制可见 Canvas ,而不绘制离屏点击 Canvas 。

activeLayer.drawScene();

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

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

activeLayer.batchDraw();

选择图层

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

myNode.transformsEnabled(“none”);

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

myNode.cache({…});

其他性能内容

…并且,n-e-v-e-r 使用阴影。阴影需要 KineticJS 和原生 html Canvas 进行特别昂贵的处理。如果您需要阴影节点:

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

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

一些有用的信息

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

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

Hope this helps and good luck with your project!

关于javascript - 关于动力学层的问题,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/22719531/

相关文章:

javascript - 将 context.drawImage() 与 MediaStream 结合使用

javascript - Fabric.js 组成员的属性不更新?

database - 随着记录的增长,mongoldb文档更新的性能下降

javascript - 检查jquery插件是否已经初始化的函数

c - 将 0 映射到任何非零值同时保留其他值的无分支方式?

Javascript - Canvas - 在先前填充的颜色上覆盖透明的 png

javascript - Chart.js 圆 Angular donut

javascript - 使用 Appcelerator Titanium 代替 Apple 开发开发 iPhone 应用程序的优点/缺点

javascript - 检查元素在 IFRAME 中是否可见

javascript - 上传从 Canvas 创建的图像