blob: 4205ef80e1298c84d2000735f9958e6cae47469c [file] [log] [blame]
Kevin Lubick5443bb32020-05-01 14:16:27 -04001<!-- This benchmark aims to accurately measure the time it takes for Skottie to load the JSON and
2turn it into an animation, as well as the times for the first hundred frames (and, as a subcomponent
3of that, the seek times of the first hundred frames). This is set to mimic how a real-world user
4would display the animation (e.g. using clock time to determine where to seek, not frame numbers).
5-->
6<!DOCTYPE html>
7<html>
8<head>
9 <title>Skottie-WASM Perf</title>
10 <meta charset="utf-8" />
11 <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
12 <meta name="viewport" content="width=device-width, initial-scale=1.0">
13 <script src="/static/canvaskit.js" type="text/javascript" charset="utf-8"></script>
14 <style type="text/css" media="screen">
15 body {
16 margin: 0;
17 padding: 0;
18 }
19 </style>
20</head>
21<body>
22 <main>
23 <button id="start_bench">Start Benchmark</button>
24 <br>
25 <canvas id=anim width=1000 height=1000 style="height: 1000px; width: 1000px;"></canvas>
26 </main>
27 <script type="text/javascript" charset="utf-8">
28 const WIDTH = 1000;
29 const HEIGHT = 1000;
30 // We sample MAX_FRAMES or until MAX_SAMPLE_SECONDS has elapsed.
31 const MAX_FRAMES = 600; // ~10s at 60fps
32 const MAX_SAMPLE_MS = 30 * 1000; // in case something takes a while, stop after 30 seconds.
33 const LOTTIE_JSON_PATH = '/static/lottie.json';
34 const ASSETS_PATH = '/static/assets/';
35 (function() {
36
37 const loadKit = CanvasKitInit({
38 locateFile: (file) => '/static/' + file,
Kevin Lubick4ad6b502020-05-21 10:51:35 -040039 });
Kevin Lubick5443bb32020-05-01 14:16:27 -040040
41 const loadLottie = fetch(LOTTIE_JSON_PATH).then((resp) => {
42 return resp.text()
43 });
44
45 const loadFontsAndAssets = loadLottie.then((jsonStr) => {
46 const lottie = JSON.parse(jsonStr);
47 const promises = [];
48 promises.push(...loadFonts(lottie.fonts));
49 promises.push(...loadAssets(lottie.assets));
50 return Promise.all(promises);
51 });
52
53 Promise.all([loadKit, loadLottie, loadFontsAndAssets]).then((values) => {
54 const [CanvasKit, json, externalAssets] = values;
55 console.log(externalAssets);
56 const assets = {};
57 for (const asset of externalAssets) {
58 if (asset) {
59 assets[asset.name] = asset.bytes;
60 }
61 }
62 const loadStart = performance.now();
63 const animation = CanvasKit.MakeManagedAnimation(json, assets);
64 const loadTime = performance.now() - loadStart;
65
66 const duration = animation.duration() * 1000;
67 const bounds = {fLeft: 0, fTop: 0, fRight: WIDTH, fBottom: HEIGHT};
68
69 const surface = getSurface(CanvasKit);
70 if (!surface) {
71 console.error('Could not make surface', window._error);
72 return;
73 }
74 const canvas = surface.getCanvas();
75
76 document.getElementById('start_bench').addEventListener('click', () => {
77 const clearColor = CanvasKit.WHITE;
78 const frames = new Float32Array(MAX_FRAMES);
79 const seeks = new Float32Array(MAX_FRAMES);
80 let idx = 0;
81 const firstFrame = Date.now();
82 function drawFrame() {
83 const now = performance.now();
84 // Actually draw stuff.
85 const seek = ((Date.now() - firstFrame) / duration) % 1.0;
86 const damage = animation.seek(seek);
87 const afterSeek = performance.now();
88
89 if (damage.fRight > damage.fLeft && damage.fBottom > damage.fTop) {
90 canvas.clear(clearColor);
91 animation.render(canvas, bounds);
92 }
93 surface.flush();
94 // FPS measurement
95 frames[idx] = performance.now() - now;
96 seeks[idx] = afterSeek - now;
97 idx++;
98 // If we have maxed out the frames we are measuring or have completed the animation,
99 // we stop benchmarking.
100 if (idx >= frames.length || (Date.now() - firstFrame) > MAX_SAMPLE_MS) {
101 window._perfData = {
102 frames_ms: Array.from(frames).slice(0, idx),
103 seeks_ms: Array.from(seeks).slice(0, idx),
104 json_load_ms: loadTime,
105 };
106 window._perfDone = true;
107 return;
108 }
109 window.requestAnimationFrame(drawFrame);
110 }
111 window.requestAnimationFrame(drawFrame);
112 });
113 console.log('Perf is ready');
114 window._perfReady = true;
115 });
116 })();
117
118 function getSurface(CanvasKit) {
119 let surface;
120 if (window.location.hash.indexOf('gpu') !== -1) {
121 surface = CanvasKit.MakeWebGLCanvasSurface('anim', WIDTH, HEIGHT);
122 if (!surface) {
123 window._error = 'Could not make GPU surface';
124 return null;
125 }
126 let c = document.getElementById('anim');
127 // If CanvasKit was unable to instantiate a WebGL context, it will fallback
128 // to CPU and add a ck-replaced class to the canvas element.
129 if (c.classList.contains('ck-replaced')) {
130 window._error = 'fell back to CPU';
131 return null;
132 }
133 } else {
134 surface = CanvasKit.MakeSWCanvasSurface('anim', WIDTH, HEIGHT);
135 if (!surface) {
136 window._error = 'Could not make CPU surface';
137 return null;
138 }
139 }
140 return surface;
141 }
142
143 function loadFonts(fonts) {
144 const promises = [];
145 if (!fonts || !fonts.list) {
146 return promises;
147 }
148 for (const font of fonts.list) {
149 if (font.fName) {
150 promises.push(fetch(`${ASSETS_PATH}/${font.fName}.ttf`).then((resp) => {
151 // fetch does not reject on 404
152 if (!resp.ok) {
153 console.error(`Could not load ${font.fName}.ttf: status ${resp.status}`);
154 return null;
155 }
156 return resp.arrayBuffer().then((buffer) => {
157 return {
158 'name': font.fName,
159 'bytes': buffer
160 };
161 });
162 })
163 );
164 }
165 }
166 return promises;
167 }
168
169 function loadAssets(assets) {
170 const promises = [];
171 for (const asset of assets) {
172 // asset.p is the filename, if it's an image.
173 // Don't try to load inline/dataURI images.
174 const should_load = asset.p && asset.p.startsWith && !asset.p.startsWith('data:');
175 if (should_load) {
176 promises.push(fetch(`${ASSETS_PATH}/${asset.p}`)
177 .then((resp) => {
178 // fetch does not reject on 404
179 if (!resp.ok) {
180 console.error(`Could not load ${asset.p}: status ${resp.status}`);
181 return null;
182 }
183 return resp.arrayBuffer().then((buffer) => {
184 return {
185 'name': asset.p,
186 'bytes': buffer
187 };
188 });
189 })
190 );
191 }
192 }
193 return promises;
194 }
195 </script>
196</body>
197</html>