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 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 = 90 * 1000; // in case something takes a while, stop after 90 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 startTime = Date.now(); 90 const damageRect = Float32Array.of(0, 0, 0, 0); 91 92 function draw() { 93 const seek = ((Date.now() - startTime) / duration) % 1.0; 94 const damage = animation.seek(seek, damageRect); 95 96 if (damage[2] > damage[0] && damage[3] > damage[1]) { 97 animation.render(canvas, bounds); 98 } 99 } 100 101 startTimingFrames(draw, surface, WARM_UP_FRAMES, MAX_FRAMES, MAX_SAMPLE_MS).then((results) => { 102 Object.assign(window._perfData, results); 103 window._perfDone = true; 104 }).catch((error) => { 105 window._error = error; 106 }); 107 108 }); 109 console.log('Perf is ready'); 110 window._perfReady = true; 111 }); 112 })(); 113 114 function loadFonts(fonts) { 115 const promises = []; 116 if (!fonts || !fonts.list) { 117 return promises; 118 } 119 for (const font of fonts.list) { 120 if (font.fName) { 121 promises.push(fetch(`${ASSETS_PATH}/${font.fName}.ttf`).then((resp) => { 122 // fetch does not reject on 404 123 if (!resp.ok) { 124 console.error(`Could not load ${font.fName}.ttf: status ${resp.status}`); 125 return null; 126 } 127 return resp.arrayBuffer().then((buffer) => { 128 return { 129 'name': font.fName, 130 'bytes': buffer 131 }; 132 }); 133 }) 134 ); 135 } 136 } 137 return promises; 138 } 139 140 function loadAssets(assets) { 141 const promises = []; 142 if (!assets) { 143 return []; 144 } 145 for (const asset of assets) { 146 // asset.p is the filename, if it's an image. 147 // Don't try to load inline/dataURI images. 148 const should_load = asset.p && asset.p.startsWith && !asset.p.startsWith('data:'); 149 if (should_load) { 150 promises.push(fetch(`${ASSETS_PATH}/${asset.p}`) 151 .then((resp) => { 152 // fetch does not reject on 404 153 if (!resp.ok) { 154 console.error(`Could not load ${asset.p}: status ${resp.status}`); 155 return null; 156 } 157 return resp.arrayBuffer().then((buffer) => { 158 return { 159 'name': asset.p, 160 'bytes': buffer 161 }; 162 }); 163 }) 164 ); 165 } 166 } 167 return promises; 168 } 169 </script> 170</body> 171</html> 172