b437d7b5e4
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>
189 lines
7.0 KiB
HTML
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> |