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