【发布时间】: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