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