41fe8bc350
Change-Id: I2c4e2e847de15c135fef24456903ff5ff819213d Reviewed-on: https://skia-review.googlesource.com/c/skia/+/292239 Reviewed-by: Kevin Lubick <kjlubick@google.com>
198 lines
6.6 KiB
HTML
198 lines
6.6 KiB
HTML
<!-- This benchmark aims to accurately measure the time it takes for Skottie to load the JSON and
|
|
turn it into an animation, as well as the times for the first hundred frames (and, as a subcomponent
|
|
of that, the seek times of the first hundred frames). This is set to mimic how a real-world user
|
|
would display the animation (e.g. using clock time to determine where to seek, not frame numbers).
|
|
-->
|
|
<!DOCTYPE html>
|
|
<html>
|
|
<head>
|
|
<title>Skottie-WASM Perf</title>
|
|
<meta charset="utf-8" />
|
|
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<script src="/static/canvaskit.js" type="text/javascript" charset="utf-8"></script>
|
|
<style type="text/css" media="screen">
|
|
body {
|
|
margin: 0;
|
|
padding: 0;
|
|
}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<main>
|
|
<button id="start_bench">Start Benchmark</button>
|
|
<br>
|
|
<canvas id=anim width=1000 height=1000 style="height: 1000px; width: 1000px;"></canvas>
|
|
</main>
|
|
<script type="text/javascript" charset="utf-8">
|
|
const WIDTH = 1000;
|
|
const HEIGHT = 1000;
|
|
// We sample MAX_FRAMES or until MAX_SAMPLE_SECONDS has elapsed.
|
|
const MAX_FRAMES = 600; // ~10s at 60fps
|
|
const MAX_SAMPLE_MS = 30 * 1000; // in case something takes a while, stop after 30 seconds.
|
|
const LOTTIE_JSON_PATH = '/static/lottie.json';
|
|
const ASSETS_PATH = '/static/assets/';
|
|
(function() {
|
|
|
|
const loadKit = CanvasKitInit({
|
|
locateFile: (file) => '/static/' + file,
|
|
});
|
|
|
|
const loadLottie = fetch(LOTTIE_JSON_PATH).then((resp) => {
|
|
return resp.text()
|
|
});
|
|
|
|
const loadFontsAndAssets = loadLottie.then((jsonStr) => {
|
|
const lottie = JSON.parse(jsonStr);
|
|
const promises = [];
|
|
promises.push(...loadFonts(lottie.fonts));
|
|
promises.push(...loadAssets(lottie.assets));
|
|
return Promise.all(promises);
|
|
});
|
|
|
|
Promise.all([loadKit, loadLottie, loadFontsAndAssets]).then((values) => {
|
|
const [CanvasKit, json, externalAssets] = values;
|
|
console.log(externalAssets);
|
|
const assets = {};
|
|
for (const asset of externalAssets) {
|
|
if (asset) {
|
|
assets[asset.name] = asset.bytes;
|
|
}
|
|
}
|
|
const loadStart = performance.now();
|
|
const animation = CanvasKit.MakeManagedAnimation(json, assets);
|
|
const loadTime = performance.now() - loadStart;
|
|
|
|
const duration = animation.duration() * 1000;
|
|
const bounds = {fLeft: 0, fTop: 0, fRight: WIDTH, fBottom: HEIGHT};
|
|
|
|
const surface = getSurface(CanvasKit);
|
|
if (!surface) {
|
|
console.error('Could not make surface', window._error);
|
|
return;
|
|
}
|
|
const canvas = surface.getCanvas();
|
|
|
|
document.getElementById('start_bench').addEventListener('click', () => {
|
|
const clearColor = CanvasKit.WHITE;
|
|
const frames = new Float32Array(MAX_FRAMES);
|
|
const seeks = new Float32Array(MAX_FRAMES);
|
|
let idx = 0;
|
|
const firstFrame = Date.now();
|
|
function drawFrame() {
|
|
const now = performance.now();
|
|
// Actually draw stuff.
|
|
const seek = ((Date.now() - firstFrame) / duration) % 1.0;
|
|
const damage = animation.seek(seek);
|
|
const afterSeek = performance.now();
|
|
|
|
if (damage.fRight > damage.fLeft && damage.fBottom > damage.fTop) {
|
|
canvas.clear(clearColor);
|
|
animation.render(canvas, bounds);
|
|
}
|
|
surface.flush();
|
|
// FPS measurement
|
|
frames[idx] = performance.now() - now;
|
|
seeks[idx] = afterSeek - now;
|
|
idx++;
|
|
// If we have maxed out the frames we are measuring or have completed the animation,
|
|
// we stop benchmarking.
|
|
if (idx >= frames.length || (Date.now() - firstFrame) > MAX_SAMPLE_MS) {
|
|
window._perfData = {
|
|
frames_ms: Array.from(frames).slice(0, idx),
|
|
seeks_ms: Array.from(seeks).slice(0, idx),
|
|
json_load_ms: loadTime,
|
|
};
|
|
window._perfDone = true;
|
|
return;
|
|
}
|
|
window.requestAnimationFrame(drawFrame);
|
|
}
|
|
window.requestAnimationFrame(drawFrame);
|
|
});
|
|
console.log('Perf is ready');
|
|
window._perfReady = true;
|
|
});
|
|
})();
|
|
|
|
function getSurface(CanvasKit) {
|
|
let surface;
|
|
if (window.location.hash.indexOf('gpu') !== -1) {
|
|
surface = CanvasKit.MakeWebGLCanvasSurface('anim');
|
|
if (!surface) {
|
|
window._error = 'Could not make GPU surface';
|
|
return null;
|
|
}
|
|
let c = document.getElementById('anim');
|
|
// If CanvasKit was unable to instantiate a WebGL context, it will fallback
|
|
// to CPU and add a ck-replaced class to the canvas element.
|
|
if (c.classList.contains('ck-replaced')) {
|
|
window._error = 'fell back to CPU';
|
|
return null;
|
|
}
|
|
} else {
|
|
surface = CanvasKit.MakeSWCanvasSurface('anim');
|
|
if (!surface) {
|
|
window._error = 'Could not make CPU surface';
|
|
return null;
|
|
}
|
|
}
|
|
return surface;
|
|
}
|
|
|
|
function loadFonts(fonts) {
|
|
const promises = [];
|
|
if (!fonts || !fonts.list) {
|
|
return promises;
|
|
}
|
|
for (const font of fonts.list) {
|
|
if (font.fName) {
|
|
promises.push(fetch(`${ASSETS_PATH}/${font.fName}.ttf`).then((resp) => {
|
|
// fetch does not reject on 404
|
|
if (!resp.ok) {
|
|
console.error(`Could not load ${font.fName}.ttf: status ${resp.status}`);
|
|
return null;
|
|
}
|
|
return resp.arrayBuffer().then((buffer) => {
|
|
return {
|
|
'name': font.fName,
|
|
'bytes': buffer
|
|
};
|
|
});
|
|
})
|
|
);
|
|
}
|
|
}
|
|
return promises;
|
|
}
|
|
|
|
function loadAssets(assets) {
|
|
const promises = [];
|
|
for (const asset of assets) {
|
|
// asset.p is the filename, if it's an image.
|
|
// Don't try to load inline/dataURI images.
|
|
const should_load = asset.p && asset.p.startsWith && !asset.p.startsWith('data:');
|
|
if (should_load) {
|
|
promises.push(fetch(`${ASSETS_PATH}/${asset.p}`)
|
|
.then((resp) => {
|
|
// fetch does not reject on 404
|
|
if (!resp.ok) {
|
|
console.error(`Could not load ${asset.p}: status ${resp.status}`);
|
|
return null;
|
|
}
|
|
return resp.arrayBuffer().then((buffer) => {
|
|
return {
|
|
'name': asset.p,
|
|
'bytes': buffer
|
|
};
|
|
});
|
|
})
|
|
);
|
|
}
|
|
}
|
|
return promises;
|
|
}
|
|
</script>
|
|
</body>
|
|
</html>
|