skia2/tools/perf-canvaskit-puppeteer/path-transform.html
Elliot Evans b437d7b5e4 Add puppeteer perf for performance degredation related to transforming a complex path. Translations
and rotations both have an effect on cache usage. Snapping translations to integer coordinations
reduces cache usage. Opacity of path painting does not have an effect on cache usage.

Bug: skia:10272
Change-Id: Id5d5f08cb43645c9ec44b9d8e5e96643041727c3
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/292280
Reviewed-by: Kevin Lubick <kjlubick@google.com>
2020-05-29 20:17:14 +00:00

189 lines
7.0 KiB
HTML

<!-- This benchmark aims to measure performance degredation related to
moving a complex path. May be related to caching an alpha mask of the path at
subpixel coordinates i.e. (25.234, 43.119) instead of (25, 43).
As a consequence the cache may get full very quickly. Effect of paint opacity
and rotation transformations on performance can also be tested using the query param options.
Available query param options:
- snap: Round all path translations to the nearest integer. This means subpixel coordinate.
translations will not be used. Only has an effect when the translating option is used.
- opacity: Use a transparent color to fill the path. If this option is
not included then opaque black is used.
- translate: The path will be randomly translated every frame.
- rotate: The path will be randomly rotated every frame.
-->
<!DOCTYPE html>
<html>
<head>
<title>Complex Path translation Perf</title>
<meta charset="utf-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<script src="/static/canvaskit.js" type="text/javascript" charset="utf-8"></script>
<style type="text/css" media="screen">
body {
margin: 0;
padding: 0;
}
#test-svg {
height: 0;
width: 0;
}
#complex-path {
height: 1000px;
width: 1000px;
}
</style>
</head>
<body>
<!-- Arbitrary svg for testing. Source: https://dev.w3.org/SVG/tools/svgweb/samples/svg-files/gallardo.svg-->
<object type="image/svg+xml" data="/static/assets/car.svg" id="test-svg">
Car image
</object>
<main>
<button id="start_bench">Start Benchmark</button>
<br>
<canvas id=complex-path width=1000 height=1000></canvas>
</main>
<script type="text/javascript" charset="utf-8">
const urlSearchParams = new URLSearchParams(window.location.search);
// We sample MAX_FRAMES or until MAX_SAMPLE_SECONDS has elapsed.
const MAX_FRAMES = 60 * 30; // ~30s at 60fps
const MAX_SAMPLE_MS = 30 * 1000; // in case something takes a while, stop after 30 seconds.
const TRANSPARENT_PINK = new Float32Array([1,0,1,0.1]);
const svgObjectElement = document.getElementById('test-svg');
svgObjectElement.addEventListener('load', () => {
CanvasKitInit({
locateFile: (file) => '/static/' + file,
}).then(run);
});
function run(CanvasKit) {
const surface = getSurface(CanvasKit);
if (!surface) {
console.error('Could not make surface', window._error);
return;
}
const skcanvas = surface.getCanvas();
const grContext = surface.grContext;
document.getElementById('start_bench').addEventListener('click', () => {
// Initialize drawing related objects
const svgElement = svgObjectElement.contentDocument;
const svgPathAndFillColorPairs = svgToPathAndFillColorPairs(svgElement, CanvasKit);
const paint = new CanvasKit.SkPaint();
paint.setAntiAlias(true);
paint.setStyle(CanvasKit.PaintStyle.Fill);
let paintColor = CanvasKit.BLACK;
// Path is large, scale canvas so entire path is visible
skcanvas.scale(0.5, 0.5);
// Initialize perf data
let currentFrameNumber = 0;
const frameTimesMs = new Float32Array(MAX_FRAMES);
let startTimeMs = performance.now();
let previousFrameTimeMs = performance.now();
const resourceCacheUsageBytes = new Float32Array(MAX_FRAMES);
const usedJSHeapSizesBytes = new Float32Array(MAX_FRAMES);
function drawFrame() {
// Draw complex path with random translations and rotations.
let randomHorizontalTranslation = 0;
let randomVerticalTranslation = 0;
let randomRotation = 0;
if (urlSearchParams.has('translate')) {
randomHorizontalTranslation = Math.random() * 50 - 25;
randomVerticalTranslation = Math.random() * 50 - 25;
}
if (urlSearchParams.has('snap')) {
randomHorizontalTranslation = Math.round(randomHorizontalTranslation);
randomVerticalTranslation = Math.round(randomVerticalTranslation);
}
if (urlSearchParams.has('opacity')) {
paintColor = TRANSPARENT_PINK;
}
if (urlSearchParams.has('rotate')) {
randomRotation = (Math.random() - 0.5) / 20;
}
skcanvas.clear(CanvasKit.WHITE);
for (const [path, color] of svgPathAndFillColorPairs) {
path.transform([Math.cos(randomRotation), -Math.sin(randomRotation), randomHorizontalTranslation,
Math.sin(randomRotation), Math.cos(randomRotation), randomVerticalTranslation,
0, 0, 1 ]);
paint.setColor(paintColor);
skcanvas.drawPath(path, paint);
}
surface.flush();
// Record perf data: measure frame times, memory usage
const currentFrameTimeMs = performance.now();
frameTimesMs[currentFrameNumber] = currentFrameTimeMs - previousFrameTimeMs;
previousFrameTimeMs = currentFrameTimeMs;
resourceCacheUsageBytes[currentFrameNumber] = grContext.getResourceCacheUsageBytes();
usedJSHeapSizesBytes[currentFrameNumber] = window.performance.memory.totalJSHeapSize;
currentFrameNumber++;
const timeSinceStart = performance.now() - startTimeMs;
if (currentFrameNumber >= MAX_FRAMES || timeSinceStart >= MAX_SAMPLE_MS) {
window._perfData = {
frames_ms: Array.from(frameTimesMs).slice(0, currentFrameNumber),
resourceCacheUsage_bytes: Array.from(resourceCacheUsageBytes).slice(0, currentFrameNumber),
usedJSHeapSizes_bytes: Array.from(usedJSHeapSizesBytes).slice(0, currentFrameNumber),
};
window._perfDone = true;
return;
}
window.requestAnimationFrame(drawFrame);
}
window.requestAnimationFrame(drawFrame);
});
console.log('Perf is ready');
window._perfReady = true;
}
function svgToPathAndFillColorPairs(svgElement, CanvasKit) {
const pathElements = Array.from(svgElement.getElementsByTagName('path'));
return pathElements.map((path) => [
CanvasKit.MakePathFromSVGString(path.getAttribute("d")),
CanvasKit.parseColorString(path.getAttribute("fill")??'#000000')
]);
}
function getSurface(CanvasKit) {
let surface;
if (window.location.hash.indexOf('gpu') !== -1) {
surface = CanvasKit.MakeWebGLCanvasSurface('complex-path');
if (!surface) {
window._error = 'Could not make GPU surface';
return null;
}
let c = document.getElementById('complex-path');
// If CanvasKit was unable to instantiate a WebGL context, it will fallback
// to CPU and add a ck-replaced class to the canvas element.
if (c.classList.contains('ck-replaced')) {
window._error = 'fell back to CPU';
return null;
}
} else {
surface = CanvasKit.MakeSWCanvasSurface('complex-path');
if (!surface) {
window._error = 'Could not make CPU surface';
return null;
}
}
return surface;
}
</script>
</body>
</html>