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