Shopify / react-native-skia

High-performance React Native Graphics using Skia

Home Page:https://shopify.github.io/react-native-skia

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Headless Performance Drop with `Group` `blendMode="multiply"`

nahn20 opened this issue ยท comments

Description

I've noticed that rendering Group with blendMode="multiply" in headless mode causes subsequent renders to take longer. This bug only occurs when using variables inside of Group. Using constant values yields consistent render performance. This only becomes noticeable for large canvas sizes at the scale of a few dozen renders, and eventually heals itself after a few hundred renders (I'm very confused by this part). From preliminary testing, it seems it steadily picks up from iteration 0, up until ~iteration 200 when the rendering time speeds up again. The range of rendering time spikes to ~100ms and drops back down to ~2ms.

Here's some of the cleaned up and truncated output to give a better picture of the performance curve.

Iteration 0 took 9ms
Iteration 20 took 12ms
Iteration 40 took 41ms
Iteration 60 took 83ms
Iteration 80 took 111ms
Iteration 100 took 131ms
Iteration 120 took 131ms
Iteration 140 took 126ms
Iteration 160 took 118ms
Iteration 180 took 109ms
Iteration 200 took 98ms
Iteration 220 took 88ms
Iteration 240 took 79ms
Iteration 260 took 69ms
Iteration 280 took 60ms
Iteration 300 took 50ms
Iteration 320 took 43ms
Iteration 340 took 34ms
Iteration 360 took 28ms
Iteration 380 took 21ms

Version

1.0.4

Steps to reproduce

I'm using an M3 Pro and running the script using bun 1.0.29, though testing with node yields the same results.

Snack, code example, screenshot, or link to a repository

import {
	Circle,
	drawOffscreen,
	Group,
	makeOffscreenSurface,
} from "@shopify/react-native-skia/lib/commonjs/headless";
import { LoadSkiaWeb } from "@shopify/react-native-skia/lib/commonjs/web/LoadSkiaWeb";

const render = ({ frame }: { frame: number }) => {
	const r = 10 * frame;

	return (
		<Group blendMode="multiply">
			<Circle cx={r} cy={r} r={r} color="cyan" />
			<Circle cx={r} cy={r} r={r} color="cyan" />
			<Circle cx={r} cy={r} r={r} color="cyan" />
		</Group>
	);
};

const memoryLeakTest = async () => {
	await LoadSkiaWeb();
	const surface = makeOffscreenSurface(1920, 1080);
	for (let i = 0; i < 1000; i++) {
		const t = performance.now();
		const image = drawOffscreen(surface, render({ frame: i }));

		image.dispose();
		console.log(`Iteration ${i} took ${performance.now() - t}ms`);
	}
	surface.dispose();
};
await memoryLeakTest();

Silly question: is this using the GPU or not? Because these numbers look quite ok if they run on CPU no? blending operation seems quite slow even on GPU (used to do things like inner shadows for instance).

For the sake of assurdness, I chucked the test onto an AWS Lambda instance running with a memory size of 10 GB and saw similar results (albeit higher latencies due to less compute, but similar curve). Lambdas don't have GPUs so I think it's safe to say no GPU for these results. I'm less concerned about the operation being slow than the strange behavior of it taking significantly more time per iteration and then sloping back down to fast render times. I have my suspicions that there's some sort of memory leak going on.

Iteration 0 took 68ms
Iteration 20 took 61ms
Iteration 40 took 200ms
Iteration 60 took 423ms
Iteration 80 took 538ms
Iteration 100 took 632ms
Iteration 120 took 635ms
Iteration 140 took 609ms
Iteration 160 took 571ms
Iteration 180 took 528ms
Iteration 200 took 481ms
Iteration 220 took 431ms
Iteration 240 took 382ms
Iteration 260 took 335ms
Iteration 280 took 288ms
Iteration 300 took 245ms
Iteration 320 took 204ms
Iteration 340 took 166ms
Iteration 360 took 132ms
Iteration 380 took 102ms

After testing it looks like the memory leaks was just slowing down the operation and blending especially on the CPU is slow enough to see such a big difference.

๐ŸŽ‰ This issue has been resolved in version 1.0.5 ๐ŸŽ‰

The release is available on:

Your semantic-release bot ๐Ÿ“ฆ๐Ÿš€