Kinetic Layers如何合成?如何,实施是什么?
起初我认为每一层都是自己的画布(文档暗示这一点),但除非这些是屏幕外的画布元素,否则似乎并非如此。如果他们在屏幕外,我仍然会得到违反直觉的性能命中。
我有一个使用Kinetic的组件有3层。称他们为backgroundLayer,activeLayer和selectionLayer。第一个在加载新文档时呈现一次;第二个很少更新,但有可能需要移动或添加/删除的元素;最后一个只有一个非常小的元素。
让我感到惊讶的是,如果我有一个20fps的循环渲染,我在其中明确地检查每个层是否有我自己的“脏标志”,并且只有在它变脏时才渲染该层,即使我只更新selectionLayer,帧率也会爬行,即使该图层已将clearBeforeDraw设置为false。我可以确认此属性阻止清除,因为移动选择会留下像素的痕迹。
阶段通常类似于600x2000,但selectionLayer中的单个元素是一个大约20x100的Rect。
我怀疑发生的是每个图层都呈现为离屏画布,但随后这些画布会合成到单个可见画布中。可能我的selectionLayer快速更新屏幕外(我可以添加清除旧矩形以保持快速),但是当合成图层时它实际上是有效地模糊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毫秒,其中,~99ms将是 Kinetic.Context.drawImage()调用[context2d。] drawImage()作为堆栈的最终调用。
这是什么电话,为什么?
我目前还不关心我可能想做的各种其他优化,包括使用单独的画布或将我的怪物UI组件切割成更多缓存友好的画布元素。我试图理解这个问题,因为它会影响我接下来要优化的选择。也就是说,所有其他优化建议都表示赞赏。
答案 0 :(得分:1)
关于KineticJS图层
每个KineticJS层实际上是2个画布。一个画布用于可视显示,另一个画布用于屏幕外工作,如命中测试和拖动操作。所以你的3层真的是3套画布(总共6幅画布)。
背景图层
如果你的背景图层永远不会改变,请告诉它不要听事件。事件系统使用大量资源,因此这应该为其他任务释放cpu时间。您甚至可以考虑将背景放在使用Kinetic容器下的CSS定位的图像元素上。这样KineticJS就不必为它分配任何资源了。
backgroundLayer.listening(false);
有效图层
如果此图层上的所有添加/删除/移动都是以编程方式完成的,并且您不需要用户单击/拖动活动图层上的任何元素,那么也可以在此图层上设置侦听。
activeLayer.listening(false);
如果您仍需要侦听活动图层事件,但不需要命中测试,请使用drawScene而不是draw。 drawScene命令仅绘制可见画布而不是屏幕外命中画布。
activeLayer.drawScene();
令人惊讶的是,您可能会发现让KineticJS清除/重绘整个activeLayer而不是使用clearBeforeDraw进行微观管理会更快。这是因为GPU可以更快地清除整个画布,而不是清除画布的一部分。画布有一个内部像素颜色数组,非常快速地填充零以擦除整个画布。要清除画布的一部分,浏览器必须计算起始像素位置并保持跟踪,因为它只填充像素数组的一部分。
在活动图层上更新多个节点时,请使用batchDraw(),它使用window.requestAnimationFrame“在场景后面”。 R.A.F.它与显示硬件集成在一起,因此减少了显示器刷新中断绘制的可能性。
activeLayer.batchDraw();
选择图层
如果选择图层上的节点未被旋转或缩放,则可以禁用此节点的转换以大大提高性能。
myNode.transformsEnabled(“none”);
如果选择图层上的节点还不是图像,请考虑将其缓存到图像中。图像可以主要由GPU进行布局,而重绘形状也需要CPU的相当大的努力。
myNode.cache({…});
其他表演资料
...而且,n-e-v-e-r使用阴影。阴影需要KineticJS和原生html画布特别昂贵的处理。如果您需要阴影节点:
如果您正在管理自己的动画,请使用requestAnimationFrame而不是setInterval / setTimeout。 RAF通过批量处理挂起的命令并将其绘制与显示的刷新周期协调来最大化性能。
一些有用的信息
阅读KineticJS的更改日志。它包含有关性能相关问题的有用信息:
https://github.com/ericdrowell/KineticJS/wiki/Change-Log
希望这对您的项目有所帮助并祝您好运!