How are Kinetic Layers composited? As in, what is the implementation?
At first I thought that each layer was its own canvas (the docs imply this), but unless these are off-screen canvas elements, that seems not to be the case. If they are off-screen, I'm still getting performance hits that are counterintuitive.
I have a component using Kinetic that has 3 layers. Call them backgroundLayer, activeLayer, and selectionLayer. The first is rendered once when a new document is loaded; the second is rarely updated but has elements that may need to be moved around or added/deleted; the last has only a single element that is very small.
What surprised me is that if I have a loop rendering at 20fps, in which I explicitly check each layer for my own "dirty flag" and only render that layer if it's dirty, the framerate crawls even if I only update selectionLayer, and even if that layer has clearBeforeDraw set to false. I can confirm that this property is preventing clearing, as the moving selection leaves a trail of pixels.
The stage is typically something like 600x2000, but the single element in selectionLayer is a Rect that is about 20x100.
What I suspect is happening is that each layer is rendered as an off-screen canvas, but then these canvases are composited together into the single visible canvas. Probably my selectionLayer is fast to update offscreen (and I could add clearing of just the old rect to keep it fast), but then it is effectively blitting 600x2000 transparent pixels when compositing the layers (or even worse, causing the 2 layers that have not been updated to be composited together as well).
Is this accurate as to what's happening? If so, is there a different approach I would take to keep my rendering fast? I'm thinking of just having a separate Kinetic.Stage (and hence canvas), but it's a workaround that starts losing some of the apparent benefits of layers. If layers are only meant to organize code but have this performance implication, I think this should be documented. It would be nice to have onscreen canvas elements per layer too, though I realize this would be a significant change to the library. But otherwise it looks like I'll need to do extra work at the DOM/CSS level to coordinate my layers and get the performance target I need.
RE-EDIT to include sample code and boil question down to its essence:
In component's 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);
In component's 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;
}
And lastly:
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;
}
Note that only the playheadLayer is set as dirty, and I have confirmed only this layer is rendered. In Chrome's profiler flame chart I can see that each single call to render() takes ~100ms, and of this, ~99ms will be Kinetic.Context.drawImage() calling [context2d.]drawImage() as the final call on the stack.
What is that call doing, and why?
I'm not at the moment concerned with various other optimizations I may well want to do, including using separate canvases or slicing my monster UI component into more cache-friendly canvas elements. I'm trying to understand this question, because it impacts what I choose to optimize next. That said, all the other optimization suggestions are appreciated.