Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[iOS] Image re-rendering with potential memory leak #2909

Open
1 task done
DImuthuUpe opened this issue Jan 23, 2025 · 2 comments
Open
1 task done

[iOS] Image re-rendering with potential memory leak #2909

DImuthuUpe opened this issue Jan 23, 2025 · 2 comments
Labels
bug Something isn't working

Comments

@DImuthuUpe
Copy link

Description

I am trying to build a pdf document viewer using react native skia and reanimated. This requires rendering bitmap tiles of a pdf page at different resolution levels when we peform pinch zoom on canvas. As a result of that, I have to regenerat the Skia Image multiple times and assign to the same image component. Please have a look at following simplified code sample. The issue I am struggling is, when I zoom in and generate images of 3000 x 3000 pixels, memory usage goes up as expected but when I zoom out and generate 300x300 bitmaps, memory does not go back down. I initially though that this is related to delayed garbage collection but it seems like not the case as the memory is occupied permanantly for a long period of time. I lookd at the memory profile and there is a lot of objects with stuck at SkData::MakeWithCopy and I think those are the images I created on demad when the pinch zoom ended. Can you help me to figure out why this memory leak is happening and what could be the potential resolution for this?

import React, { useEffect, useState } from 'react';
import { View, StyleSheet } from 'react-native';
import { Canvas, Skia, AlphaType, ColorType, Fill, Image, SkImage } from '@shopify/react-native-skia';
import { GestureDetector, Gesture, GestureHandlerRootView } from 'react-native-gesture-handler';
import Animated, {
  useSharedValue,
  useDerivedValue,
  runOnJS,
} from 'react-native-reanimated';

function App(): React.JSX.Element {

  const tileSize = 512;
  const scale = useSharedValue(1);
  const translationX = useSharedValue(0);
  const translationY = useSharedValue(0);
  const [scaleEnd, setScaleEnd] = useState(1);
  const [skiaImage, setSkiaImage] = useState<SkImage>();

  const createTile = (tileSize: number) => {
      const pixels = new Uint8Array(tileSize * tileSize * 4);
      pixels.fill(255);
      let i = 0;
      for (let x = 0; x < tileSize; x++) {
        for (let y = 0; y < tileSize; y++) {
          pixels[i++] = (x * y) % 255;
        }
      }
      const data = Skia.Data.fromBytes(pixels);
      const img = Skia.Image.MakeImage(
        {
          width: tileSize,
          height: tileSize,
          alphaType: AlphaType.Opaque,
          colorType: ColorType.RGBA_8888,
        },
        data,
        tileSize * 4
      );
  
      return img;
    }


  if (skiaImage === null) {
    return <View style={styles.container} />;
  }

  const panGesture = Gesture.Pan().onChange((e) => {
    translationX.value += e.changeX;
    translationY.value += e.changeY;
  });
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                   
  const scaleGesture = Gesture.Pinch().onChange((e) => {
    scale.value +=  e.scaleChange - 1;
  }).onEnd(() => { 
    runOnJS(() => {
      setScaleEnd(scale.value);
    })();
  }).runOnJS(true);

  useEffect(() => { 
    const skImg = createTile(tileSize * scale.value);
    console.log("Skia image calculating for tile size " + tileSize * scale.value);
    if (skImg !== null) {
      setSkiaImage(skImg);
    } 
  }, [scaleEnd]);

  return (
    <GestureHandlerRootView>
      <View style={{ flex: 1 }}>
        <Canvas style={{ flex: 1 }}>
          <Fill color="pink" />
          <Image image={skiaImage} x={translationX} y={translationY} width={useDerivedValue(() => tileSize * scale.value)} height={useDerivedValue(() => tileSize * scale.value)} />
        </Canvas>
        <GestureDetector gesture={Gesture.Race(panGesture, scaleGesture)}>
          <Animated.View style={StyleSheet.absoluteFill} />
        </GestureDetector>
      </View>
    </GestureHandlerRootView>
  );
};

const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: "pink",
  },
  canvasContainer: {
    flex: 1,
    justifyContent: "center",
    alignItems: "center",
  },
  canvas: {
    flex: 1,
  },
  animatedCanvas: {
    flex: 1,
  },
});

export default App;

React Native Skia Version

1.9.0

React Native Version

0.76.5

Using New Architecture

  • Enabled

Steps to Reproduce

Run the attached code and try multiple pinch zoom on iOS

Snack, Code Example, Screenshot, or Link to Repository

Image Image
@DImuthuUpe DImuthuUpe added the bug Something isn't working label Jan 23, 2025
@wcandillon
Copy link
Contributor

wcandillon commented Jan 23, 2025 via email

@DImuthuUpe
Copy link
Author

@wcandillon I tried to dispose the old image but that raised some EXEC_BAD_ACCESS errors on C++. Please let me whether following way is correct?

useEffect(() => { 
    const skImg = createTile(tileSize * scale.value);
    console.log("Skia image calculating for tile size " + tileSize * scale.value);
    if (skImg !== null) {
      const oldSkiaImage = skiaImage;
      setSkiaImage(skImg);
      if (oldSkiaImage) {
        oldSkiaImage.dispose();
      }
    } 
  }, [scaleEnd]);

Image

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working
Projects
None yet
Development

No branches or pull requests

2 participants