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

Super slow rendering when using useFont #1562

Closed
msageryd opened this issue May 10, 2023 · 16 comments
Closed

Super slow rendering when using useFont #1562

msageryd opened this issue May 10, 2023 · 16 comments
Labels
question Further information is requested

Comments

@msageryd
Copy link

Description

I'm rendering 1000 shapes on a Skia canvas. The shapes looks like this:

  const font = useFont(caveatBold, fontSize);

  return (
    <Group>
      <Line
        p1={vec(p1.x * flagScale, p1.y * flagScale)}
        p2={vec(p2.x * flagScale, p2.y * flagScale)}
        color="darkgreen"
        style="stroke"
        strokeWidth={1 * flagScale}
      />
      <RoundedRect
        x={p2.x * flagScale}
        y={p2.y * flagScale}
        width={12 * flagScale}
        height={8 * flagScale}
        r={3 * flagScale}
        color="lightblue"
      />
      <RoundedRect
        x={(p2.x + 1) * flagScale}
        y={(p2.y + 1) * flagScale}
        width={10 * flagScale}
        height={6 * flagScale}
        r={3 * flagScale}
        color="green"
      />
      <Text
        x={p2.x * flagScale + 2}
        y={(p2.y + 6) * flagScale}
        text={identifier.toString()}
        font={font}
        color={'black'}
      />
    </Group>

The first render, i.e. before all shapes are rendered takes a very long time (many seconds).

If I remove the <Text> component the render is still slow.

If I also remove useFont the render is really fast.

All shapes should have the same font. Is there any way to load the font outside of the component so I don't have to do it for every shepe?

Version

0.1.188

Steps to reproduce

Please see description.

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

https://snack.expo.dev/@msageryd/skia_usefont?platform=ios

@msageryd msageryd added the bug Something isn't working label May 10, 2023
@msageryd
Copy link
Author

I tried to load the font in the main component, i.e. outside of the loop, and gave the font to my Flag component via props. This solved the problem.

Is useFont something I should be careful with?
In that case, maybe update the docs to warn about usage in loops.

@chrfalch
Copy link
Contributor

Hi @msageryd - Happy to hear that you fixed it! A good rule of thumb is to watch resource usage - and fonts are considered resources. In future versions we'll try to provide some tools to help with fonts - maybe adding font managers, access to system fonts etc.

@chrfalch chrfalch added question Further information is requested and removed bug Something isn't working labels May 11, 2023
@msageryd
Copy link
Author

I think it would be great to somehow speed up the font handling. I don't think it will be uncommon to have an encapsulated component including some text. If such a component is rendered many times, trouble is ahead.

My workaround will be cumbersome when multiple fonts and font sizes are needed in the component, since I have to useFont for each of the needed fonts outside of the component and send the fonts in as props.

@chrfalch
Copy link
Contributor

Absolutely - and we're working on some big improvements for our text and font handling for future versions :)

@msageryd
Copy link
Author

Sound great.

You have a great thing going on here. I made a apike to see what performance gains we cound get if we migrate from react-native-svg. In combination with reanimated and gesture-handler, it seems to be much to gain.

BTW. I'm a bit confused about useValue vs useSharedValue. Since rn-skia now is compatible with reanimated sharedValues, it seems to me like I could stop using useValue. It would be nicer to not mix someSkiaValue.current with someSharedValue.value in the code for clarity. Are there any drawbacks to only use sharedValues?

@chrfalch
Copy link
Contributor

Thanks for the nice feedback - really appreciate it. Regarding values, our internal values are optimised for Skia rendering on the native level - so in the future you'll probl. be driving the Skia Animation from a Shared REA value, and build the animation with Skia values. We'll get back with some more information :)

@msageryd
Copy link
Author

I actually have a lot of feedback and a lot of questions, but this feels like the wrong place. Where would you prefer to get feedback and questions? SO?

Anyway, here is an example when I got a bit confused. Maybe a "best practices" would be good:

I'm using GestureDetector for pan and pinch.
At first I used a Skia.Matrix(), which I updated from useSharedValue via useDerivedValue when my identidy4 matrix was updated.
Using a matrix instead of a bunch of loose variables (translateX, startX, scale, etc) gives nicer code, but:

  • I'm questioning if recreating skMatrix all the time might be a bottleneck
  • Matrix became a PITA to me when I wanted to withDecay the translation. I needed to manually build a new identity4 and multiply again and then create skMatrix from this.

I'm now back to a bunch of loose variables instead of a matrix. All of those variables are sharedValues for dev convenience.

Sorry for all the details. My real question is: Will there be a "best practice" or something to guide through decisions per the above? It's very exciting to get access to native drawing this easily, but it's also not clear to me what pitfalls may make me miss some of the gains.

@wcandillon
Copy link
Contributor

@msageryd what @chrfalch mentioned above will help with that, however your code as you describe it shouldn't be that slow (while we do agree that the matrix allocation on every frame is less than optimal, we're still getting great performance on our examples with Matrix4). Could you share some code snippet? Usually the best place is to discuss these things at https://github.com/Shopify/react-native-skia/discussions

@msageryd
Copy link
Author

@wcandillon there is a Snack in my first post in this thread

@wcandillon
Copy link
Contributor

I meant the Matrix + gesture handler example

@msageryd
Copy link
Author

We actually discussed this a while ago, here:
wcandillon/can-it-be-done-in-react-native#174

I tried to use withDecay on separate elements in the matrix, but didn't quite get it to work. I might have misunderstood how to solve this, but my attempt led to quite ugly code. In comparisson with using separate values for each transform instead of a matrix.

The separate values concept has the upper hand in my book, for the moment.

  • easier to reason about the code when you are not fluent in matrix algebra

  • easier (for me) to imlement withDecay

  • seemingly more optimized since I don't recreate the matrix4 all the time when I go between matrix4 and Skia.matrix via toMatrix3

The matrix concept is much more elegant, and I would love to use it if I knew:

  • the recreation of Skia.matrix will not degrade performance

  • withDecay is solvable in a straight forward way

  • I could figure out how to use clamping with the matrix so the pan/zoom are limited to the image boundaries

@wcandillon
Copy link
Contributor

this is a sensible use-case, I will try to play with it. My plan is to publish a tutorial for each demos shown at App.js for the pinch/rotate demo, I will try to add an effect where when you end the gesture, we apply an animation (reset to identity matrix). Probably you need an animation value to drive the animation (withDecay for instance) and then some linear interpolation between two matrix (a bit like this example for color matrices: https://shopify.github.io/react-native-skia/docs/color-filters/#lerp

@msageryd
Copy link
Author

Do you mean that the Lerp component could be used for arbitrary matrices and not just colors?

That would be a neat solution. So the t in Lerp would be a sharedValue which would decay and the two matrices would be the last transform "snapshot" and an identity matrix? How would you get hold of the interpolated matrix during the animation? I suppose that the interpolated matrix should be multiplied with the current transform in a useDerivedValue?

With this solution I suppose that the decay would be applied to every part of the transform, i.e. like a pan "fling" but also able to "fling" a rotation, etc?

@msageryd
Copy link
Author

Actually, wouldn't it be better with a new function lerpArray, which could interpolate between array elements? A higher order function, toIdentity4WithDecay could use lerpArray?

My problem is that I don't quite understand how to do this natively. I've not yet grasped the hole picture of sharedValues vs Skia values and JS vs Native in handlig those values.

@wcandillon
Copy link
Contributor

I think you would need to write something like this:

const lerp = (skm1: SkMatrix, skm2: SkMatrix, t: number) => {
   "worklet";
   const m1 = skm1.get();
   const m2 = skm2.get();
    if (m1.length !== 9 || m2.length !== 9) {
        throw new Error('Both matrices should be flat arrays of length 9.');
    }

    let result: SkMatrix = new Array(9).fill(0);

    for (let i = 0; i < 9; i++) {
        result[i] = m1[i] * (1 - t) + m2[i] * t;
    }

    return Skia.Matrix(result);
}

@msageryd
Copy link
Author

Thanks @wcandillon I'll play with this as soon as I get time.
It's so cool that we can get work done on the UI thread this easily.

I'm still confused regarding SkMatrix vs redash.matrix. My code is heavily influenced by your Hello example. The matrix multiplication is performed on an identity4 matrix using multiply4 and then converted to SkMatrix. Why can't the matrix calculations be performed directly with SkMatrix? The code gets quite hard to follow when I go back and forth betwen the two matrix types.

Also, I'm having great use of your redash library. But I get the feeling that these function should be available directly in rn-skia but for SkMatrix. Is this felling correct? Or should I implement redash in my code as if this library will be long lived and needed for a long time. Some clarity and maybe a roadmap for SkMatrix vs redash would be awesome.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
question Further information is requested
Projects
None yet
Development

No branches or pull requests

3 participants