1<!-- 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  <script src="/static/benchmark.js" type="text/javascript" charset="utf-8"></script>
15  <style type="text/css" media="screen">
16    body {
17      margin: 0;
18      padding: 0;
19    }
20  </style>
21</head>
22<body>
23  <main>
24    <button id="start_bench">Start Benchmark</button>
25    <br>
26    <canvas id=anim width=1000 height=1000 style="height: 1000px; width: 1000px;"></canvas>
27  </main>
28  <script type="text/javascript" charset="utf-8">
29    const WIDTH  = 1000;
30    const HEIGHT = 1000;
31    const WARM_UP_FRAMES = 0; // No warmup, so that the jank of initial frames gets's measured.
32    // We sample MAX_FRAMES or until MAX_SAMPLE_SECONDS has elapsed.
33    const MAX_FRAMES = 600; // ~10s at 60fps
34    const MAX_SAMPLE_MS = 45 * 1000; // in case something takes a while, stop after 30 seconds.
35    const LOTTIE_JSON_PATH = '/static/lottie.json';
36    const ASSETS_PATH = '/static/assets/';
37    (function() {
38
39      const loadKit = CanvasKitInit({
40        locateFile: (file) => '/static/' + file,
41      });
42
43      const loadLottie = fetch(LOTTIE_JSON_PATH).then((resp) => {
44        return resp.text();
45      });
46
47      const loadFontsAndAssets = loadLottie.then((jsonStr) => {
48        const lottie = JSON.parse(jsonStr);
49        const promises = [];
50        promises.push(...loadFonts(lottie.fonts));
51        promises.push(...loadAssets(lottie.assets));
52        return Promise.all(promises);
53      });
54
55      Promise.all([loadKit, loadLottie, loadFontsAndAssets]).then((values) => {
56        const [CanvasKit, json, externalAssets] = values;
57        console.log(externalAssets);
58        const assets = {};
59        for (const asset of externalAssets) {
60          if (asset) {
61            assets[asset.name] = asset.bytes;
62          }
63        }
64        const loadStart = performance.now();
65        const animation = CanvasKit.MakeManagedAnimation(json, assets);
66        const loadTime = performance.now() - loadStart;
67
68        window._perfData = {
69          json_load_ms: loadTime,
70        };
71
72        const duration = animation.duration() * 1000;
73        const bounds = CanvasKit.LTRBRect(0, 0, WIDTH, HEIGHT);
74
75        const urlSearchParams = new URLSearchParams(window.location.search);
76        let glversion = 2;
77        if (urlSearchParams.has('webgl1')) {
78          glversion = 1;
79        }
80
81        const surface = getSurface(CanvasKit, glversion);
82        if (!surface) {
83          console.error('Could not make surface', window._error);
84          return;
85        }
86        const canvas = surface.getCanvas();
87
88        document.getElementById('start_bench').addEventListener('click', async () => {
89          const clearColor = CanvasKit.WHITE;
90          const startTime = Date.now();
91          const damageRect = Float32Array.of(0, 0, 0, 0);
92
93          function draw() {
94            const seek = ((Date.now() - startTime) / duration) % 1.0;
95            const damage = animation.seek(seek, damageRect);
96
97            if (damage[2] > damage[0] && damage[3] > damage[1]) {
98              canvas.clear(clearColor);
99              animation.render(canvas, bounds);
100            }
101          }
102
103          startTimingFrames(draw, surface, WARM_UP_FRAMES, MAX_FRAMES, MAX_SAMPLE_MS).then((results) => {
104            Object.assign(window._perfData, results);
105            window._perfDone = true;
106          }).catch((error) => {
107            window._error = error;
108          });
109
110        });
111        console.log('Perf is ready');
112        window._perfReady = true;
113      });
114    })();
115
116  function loadFonts(fonts) {
117    const promises = [];
118    if (!fonts || !fonts.list) {
119      return promises;
120    }
121    for (const font of fonts.list) {
122      if (font.fName) {
123        promises.push(fetch(`${ASSETS_PATH}/${font.fName}.ttf`).then((resp) => {
124            // fetch does not reject on 404
125            if (!resp.ok) {
126              console.error(`Could not load ${font.fName}.ttf: status ${resp.status}`);
127              return null;
128            }
129            return resp.arrayBuffer().then((buffer) => {
130              return {
131                'name': font.fName,
132                'bytes': buffer
133              };
134            });
135          })
136        );
137      }
138    }
139    return promises;
140  }
141
142  function loadAssets(assets) {
143    const promises = [];
144    for (const asset of assets) {
145      // asset.p is the filename, if it's an image.
146      // Don't try to load inline/dataURI images.
147      const should_load = asset.p && asset.p.startsWith && !asset.p.startsWith('data:');
148      if (should_load) {
149        promises.push(fetch(`${ASSETS_PATH}/${asset.p}`)
150          .then((resp) => {
151            // fetch does not reject on 404
152            if (!resp.ok) {
153              console.error(`Could not load ${asset.p}: status ${resp.status}`);
154              return null;
155            }
156            return resp.arrayBuffer().then((buffer) => {
157              return {
158                'name': asset.p,
159                'bytes': buffer
160              };
161            });
162          })
163        );
164      }
165    }
166    return promises;
167  }
168  </script>
169</body>
170</html>
171