1<!-- This benchmark aims to measure performance degredation related to 2moving a complex path. May be related to caching an alpha mask of the path at 3subpixel coordinates i.e. (25.234, 43.119) instead of (25, 43). 4As a consequence the cache may get full very quickly. Effect of paint opacity 5and rotation transformations on performance can also be tested using the query param options. 6 7Available query param options: 8 - snap: Round all path translations to the nearest integer. This means subpixel coordinate. 9 translations will not be used. Only has an effect when the translating option is used. 10 - opacity: Use a transparent color to fill the path. If this option is 11 not included then opaque black is used. 12 - translate: The path will be randomly translated every frame. 13 - rotate: The path will be randomly rotated every frame. 14--> 15<!DOCTYPE html> 16<html> 17<head> 18 <title>Complex Path translation Perf</title> 19 <meta charset="utf-8" /> 20 <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1"> 21 <meta name="viewport" content="width=device-width, initial-scale=1.0"> 22 <script src="/static/canvaskit.js" type="text/javascript" charset="utf-8"></script> 23 <style type="text/css" media="screen"> 24 body { 25 margin: 0; 26 padding: 0; 27 } 28 #test-svg { 29 height: 0; 30 width: 0; 31 } 32 #complex-path { 33 height: 1000px; 34 width: 1000px; 35 } 36 </style> 37</head> 38<body> 39 <!-- Arbitrary svg for testing. Source: https://dev.w3.org/SVG/tools/svgweb/samples/svg-files/gallardo.svg--> 40 <object type="image/svg+xml" data="/static/assets/car.svg" id="test-svg"> 41 Car image 42 </object> 43 44 <main> 45 <button id="start_bench">Start Benchmark</button> 46 <br> 47 <canvas id=complex-path width=1000 height=1000></canvas> 48 </main> 49 <script type="text/javascript" charset="utf-8"> 50 const urlSearchParams = new URLSearchParams(window.location.search); 51 52 // We sample MAX_FRAMES or until MAX_SAMPLE_SECONDS has elapsed. 53 const MAX_FRAMES = 60 * 30; // ~30s at 60fps 54 const MAX_SAMPLE_MS = 30 * 1000; // in case something takes a while, stop after 30 seconds. 55 const TRANSPARENT_PINK = new Float32Array([1,0,1,0.1]); 56 57 const svgObjectElement = document.getElementById('test-svg'); 58 svgObjectElement.addEventListener('load', () => { 59 CanvasKitInit({ 60 locateFile: (file) => '/static/' + file, 61 }).then(run); 62 }); 63 64 function run(CanvasKit) { 65 66 const surface = getSurface(CanvasKit); 67 if (!surface) { 68 console.error('Could not make surface', window._error); 69 return; 70 } 71 const skcanvas = surface.getCanvas(); 72 const grContext = surface.grContext; 73 74 document.getElementById('start_bench').addEventListener('click', () => { 75 // Initialize drawing related objects 76 const svgElement = svgObjectElement.contentDocument; 77 const svgPathAndFillColorPairs = svgToPathAndFillColorPairs(svgElement, CanvasKit); 78 79 const paint = new CanvasKit.Paint(); 80 paint.setAntiAlias(true); 81 paint.setStyle(CanvasKit.PaintStyle.Fill); 82 let paintColor = CanvasKit.BLACK; 83 84 // Path is large, scale canvas so entire path is visible 85 skcanvas.scale(0.5, 0.5); 86 87 // Initialize perf data 88 let currentFrameNumber = 0; 89 const frameTimesMs = new Float32Array(MAX_FRAMES); 90 let startTimeMs = performance.now(); 91 let previousFrameTimeMs = performance.now(); 92 93 const resourceCacheUsageBytes = new Float32Array(MAX_FRAMES); 94 const usedJSHeapSizesBytes = new Float32Array(MAX_FRAMES); 95 96 function drawFrame() { 97 // Draw complex path with random translations and rotations. 98 let randomHorizontalTranslation = 0; 99 let randomVerticalTranslation = 0; 100 let randomRotation = 0; 101 102 if (urlSearchParams.has('translate')) { 103 randomHorizontalTranslation = Math.random() * 50 - 25; 104 randomVerticalTranslation = Math.random() * 50 - 25; 105 } 106 if (urlSearchParams.has('snap')) { 107 randomHorizontalTranslation = Math.round(randomHorizontalTranslation); 108 randomVerticalTranslation = Math.round(randomVerticalTranslation); 109 } 110 if (urlSearchParams.has('opacity')) { 111 paintColor = TRANSPARENT_PINK; 112 } 113 if (urlSearchParams.has('rotate')) { 114 randomRotation = (Math.random() - 0.5) / 20; 115 } 116 117 skcanvas.clear(CanvasKit.WHITE); 118 for (const [path, color] of svgPathAndFillColorPairs) { 119 path.transform([Math.cos(randomRotation), -Math.sin(randomRotation), randomHorizontalTranslation, 120 Math.sin(randomRotation), Math.cos(randomRotation), randomVerticalTranslation, 121 0, 0, 1 ]); 122 paint.setColor(paintColor); 123 skcanvas.drawPath(path, paint); 124 } 125 surface.flush(); 126 127 // Record perf data: measure frame times, memory usage 128 const currentFrameTimeMs = performance.now(); 129 frameTimesMs[currentFrameNumber] = currentFrameTimeMs - previousFrameTimeMs; 130 previousFrameTimeMs = currentFrameTimeMs; 131 132 resourceCacheUsageBytes[currentFrameNumber] = grContext.getResourceCacheUsageBytes(); 133 usedJSHeapSizesBytes[currentFrameNumber] = window.performance.memory.totalJSHeapSize; 134 currentFrameNumber++; 135 136 const timeSinceStart = performance.now() - startTimeMs; 137 if (currentFrameNumber >= MAX_FRAMES || timeSinceStart >= MAX_SAMPLE_MS) { 138 window._perfData = { 139 frames_ms: Array.from(frameTimesMs).slice(0, currentFrameNumber), 140 resourceCacheUsage_bytes: Array.from(resourceCacheUsageBytes).slice(0, currentFrameNumber), 141 usedJSHeapSizes_bytes: Array.from(usedJSHeapSizesBytes).slice(0, currentFrameNumber), 142 }; 143 window._perfDone = true; 144 return; 145 } 146 window.requestAnimationFrame(drawFrame); 147 } 148 window.requestAnimationFrame(drawFrame); 149 }); 150 151 console.log('Perf is ready'); 152 window._perfReady = true; 153 } 154 155 function svgToPathAndFillColorPairs(svgElement, CanvasKit) { 156 const pathElements = Array.from(svgElement.getElementsByTagName('path')); 157 return pathElements.map((path) => [ 158 CanvasKit.MakePathFromSVGString(path.getAttribute("d")), 159 CanvasKit.parseColorString(path.getAttribute("fill")??'#000000') 160 ]); 161 } 162 163 function getSurface(CanvasKit) { 164 let surface; 165 if (window.location.hash.indexOf('gpu') !== -1) { 166 surface = CanvasKit.MakeWebGLCanvasSurface('complex-path'); 167 if (!surface) { 168 window._error = 'Could not make GPU surface'; 169 return null; 170 } 171 let c = document.getElementById('complex-path'); 172 // If CanvasKit was unable to instantiate a WebGL context, it will fallback 173 // to CPU and add a ck-replaced class to the canvas element. 174 if (c.classList.contains('ck-replaced')) { 175 window._error = 'fell back to CPU'; 176 return null; 177 } 178 } else { 179 surface = CanvasKit.MakeSWCanvasSurface('complex-path'); 180 if (!surface) { 181 window._error = 'Could not make CPU surface'; 182 return null; 183 } 184 } 185 return surface; 186 } 187 </script> 188</body> 189</html> 190