• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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