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