Shopify / react-native-skia

High-performance React Native Graphics using Skia
https://shopify.github.io/react-native-skia
MIT License
6.98k stars 452 forks source link

Atlas Api performs worse than Picture Api #2688

Open itsramiel opened 1 month ago

itsramiel commented 1 month ago

Description

Hi, first of all thanks for the great library!!

The issue is that Atlas is said to be more efficient at rendering the same instance multiple times but unfortunately that is not the case for me. For example I tried creating 1000 circles that are falling down, so it is the same image but different transformations and the Picture API while sill has poor performance, was better than Atlas.

Note that I testing on an Android, Samsung Galaxy A34.

So here is an example using Picture:

function App() {
  const size = useSharedValue({width: 0, height: 0});

  const circles = useSharedValue<Array<{x: number; y: number}>>([]);

  useAnimatedReaction(
    () => size.value,
    currentSize => {
      circles.value = Array.from({length: 1000}).map(
        (): SkPoint => ({
          x: getRandomNumber(CIRCLE_RADIUS, currentSize.width - CIRCLE_RADIUS),
          y: getRandomNumber(CIRCLE_RADIUS, currentSize.height - CIRCLE_RADIUS),
        }),
      );
    },
  );

  useFrameCallback(info => {
    if (!info.timeSincePreviousFrame) return;
    const timeSincePreviousFrame = info.timeSincePreviousFrame;

    circles.modify(circles => {
      circles.forEach(circle => {
        circle.y += CIRCLE_SPEED * timeSincePreviousFrame;

        if (circle.y > size.value.height - CIRCLE_RADIUS) {
          circle.y = -CIRCLE_RADIUS;
          circle.x = getRandomNumber(
            CIRCLE_RADIUS,
            size.value.width - CIRCLE_RADIUS,
          );
        }
      });
      return circles;
    });
  });

  const picture = useDerivedValue(() => {
    return createPicture(canvas => {
      const paint = Skia.Paint();

      paint.setColor(Skia.Color('white'));

      circles.value.forEach(circle => {
        canvas.drawCircle(circle.x, circle.y, CIRCLE_RADIUS, paint);
      });
    });
  }, []);

  return (
    <View style={styles.screen}>
      <Canvas onSize={size} style={styles.canvas}>
        <Picture picture={picture} />
      </Canvas>
    </View>
  );
}

And here is the Atlas version:

const image = drawAsImage(
  <Circle cx={0} cy={0} r={CIRCLE_RADIUS} color={'white'} />,
  {
    width: 2 * CIRCLE_RADIUS,
    height: 2 * CIRCLE_RADIUS,
  },
);

const sprites = Array.from({length: CIRCLE_COUNT}).map(
  (): SkRect => rect(0, 0, CIRCLE_RADIUS * 2, CIRCLE_RADIUS * 2),
);

function getRandomNumber(min: number, max: number) {
  'worklet';
  return Math.random() * (max - min) + min;
}

function App() {
  const size = useSharedValue({width: 0, height: 0});

  const circles = useSharedValue<Array<{x: number; y: number}>>([]);

  useAnimatedReaction(
    () => size.value,
    currentSize => {
      circles.value = Array.from({length: 1000}).map(
        (): SkPoint => ({
          x: getRandomNumber(CIRCLE_RADIUS, currentSize.width - CIRCLE_RADIUS),
          y: getRandomNumber(CIRCLE_RADIUS, currentSize.height - CIRCLE_RADIUS),
        }),
      );
    },
  );

  useFrameCallback(info => {
    if (!info.timeSincePreviousFrame) return;
    const timeSincePreviousFrame = info.timeSincePreviousFrame;

    circles.modify(circles => {
      circles.forEach(circle => {
        circle.y += CIRCLE_SPEED * timeSincePreviousFrame;

        if (circle.y > size.value.height - CIRCLE_RADIUS) {
          circle.y = -CIRCLE_RADIUS;
          circle.x = getRandomNumber(
            CIRCLE_RADIUS,
            size.value.width - CIRCLE_RADIUS,
          );
        }
      });
      return circles;
    });
  });

  const transforms = useDerivedValue(() => {
    return circles.value.map(circle => Skia.RSXform(1, 0, circle.x, circle.y));
  }, []);

  return (
    <View style={styles.screen}>
      <Canvas onSize={size} style={styles.canvas}>
        <Atlas image={image} sprites={sprites} transforms={transforms} />
      </Canvas>
    </View>
  );
}
Atlas Picture

Not sure also why the output looks different, Atlas circles are smaller? But anyways perf is the bug at the moment

Version

1.4.2

Steps to reproduce

Clone the repo provided and checkout the main branch which uses the picture api and the atlas branch which uses the Atlas Api on an Android device? Perhaps a low end if possible eg: Samsung A34

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

Picture Atlas