1a4107a32a
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>
107 lines
4.4 KiB
JavaScript
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;
|
|
}
|