skia2/demos.skia.org/demos/path_performance/main.js
Elliot Evans 1a4107a32a Add path rendering performance demo to demos.skia.org
In this demo the user may choose between one of three path rendering methods
    1. SVG
    2. Canvas2D Path2D API
    3. CanvasKit

SVGs is animated using css transforms on the main thread, while Canvas2D and CanvasKit are animated
in a worker using OffscreenCanvas.

While the user views the result of the rendering, the demo collects framerate data and displays it
so the user may compare the performance of the three methods.

Change-Id: I8cd6e079bab8815614e09a276cfe78bee9557fda
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/309327
Reviewed-by: Kevin Lubick <kjlubick@google.com>
2020-08-11 19:57:08 +00:00

107 lines
4.4 KiB
JavaScript

const DEFAULT_METHOD = 'SVG';
const worker = new Worker('worker.js');
const svgObjectElement = document.getElementById('svg');
document.getElementById('svg').addEventListener('load', () => {
const svgElement = svgObjectElement.contentDocument;
const svgData = svgToPathStringAndFillColorPairs(svgElement);
// Send svgData and transfer an offscreenCanvas to the worker for Path2D and CanvasKit rendering
const path2dCanvas =
document.getElementById('Path2D-canvas').transferControlToOffscreen();
worker.postMessage({
svgData: svgData,
offscreenCanvas: path2dCanvas,
type: 'Path2D'
}, [path2dCanvas]);
const canvasKitCanvas =
document.getElementById('CanvasKit-canvas').transferControlToOffscreen();
worker.postMessage({
svgData: svgData,
offscreenCanvas: canvasKitCanvas,
type: 'CanvasKit'
}, [canvasKitCanvas]);
// The Canvas2D and CanvasKit rendering methods are executed in a web worker to avoid blocking
// the main thread. The SVG rendering method is executed in the main thread. SVG rendering is
// not in a worker because it is not possible - the DOM cannot be accessed from a web worker.
const svgAnimator = new Animator();
svgAnimator.renderer = new SVGRenderer(svgObjectElement);
switchRenderMethodCallback(DEFAULT_METHOD)();
// Listen to framerate reports from the worker, and update framerate text
worker.addEventListener('message', ({ data: {renderMethod, framesCount, totalFramesMs} }) => {
const fps = fpsFromFramesInfo(framesCount, totalFramesMs);
let textEl;
if (renderMethod === 'Path2D') {
textEl = document.getElementById('Path2D-fps');
}
if (renderMethod === 'CanvasKit') {
textEl = document.getElementById('CanvasKit-fps');
}
textEl.innerText = `${fps.toFixed(2)} fps over ${framesCount} frames`;
});
// Update framerate text every second
setInterval(() => {
if (svgAnimator.framesCount > 0) {
const fps = fpsFromFramesInfo(svgAnimator.framesCount, svgAnimator.totalFramesMs);
document.getElementById('SVG-fps').innerText =
`${fps.toFixed(2)} fps over ${svgAnimator.framesCount} frames`;
}
}, 1000);
document.getElementById('SVG-input')
.addEventListener('click', switchRenderMethodCallback('SVG'));
document.getElementById('Path2D-input')
.addEventListener('click', switchRenderMethodCallback('Path2D'));
document.getElementById('CanvasKit-input')
.addEventListener('click', switchRenderMethodCallback('CanvasKit'));
function switchRenderMethodCallback(switchMethod) {
return () => {
// Hide all renderer elements and stop svgAnimator
document.getElementById('CanvasKit-canvas').style.visibility = 'hidden';
document.getElementById('Path2D-canvas').style.visibility = 'hidden';
for (const svgEl of svgAnimator.renderer.svgElArray) {
svgEl.style.visibility = 'hidden';
}
svgAnimator.stop();
// Show only the active renderer element
if (switchMethod === 'SVG') {
svgAnimator.start();
for (const svgEl of svgAnimator.renderer.svgElArray) {
svgEl.style.visibility = 'visible';
}
}
if (switchMethod === 'CanvasKit') {
document.getElementById('CanvasKit-canvas').style.visibility = 'visible';
}
if (switchMethod === 'Path2D') {
document.getElementById('Path2D-canvas').style.visibility = 'visible';
}
worker.postMessage({ switchMethod });
};
}
});
// Add .data after the load listener so that the listener always fires an event
svgObjectElement.data = 'garbage.svg';
const EMPTY_SVG_PATH_STRING = 'M 0 0';
const COLOR_WHITE = '#000000';
function svgToPathStringAndFillColorPairs(svgElement) {
const pathElements = Array.from(svgElement.getElementsByTagName('path'));
return pathElements.map((path) => [
path.getAttribute('d') ?? EMPTY_SVG_PATH_STRING,
path.getAttribute('fill') ?? COLOR_WHITE
]);
}
const MS_IN_A_SECOND = 1000;
function fpsFromFramesInfo(framesCount, totalFramesMs) {
const averageFrameTime = totalFramesMs / framesCount;
return (1 / averageFrameTime) * MS_IN_A_SECOND;
}