Performance

Measured against react-native-skia, and what keeps a scene cheap.

Benchmark

20,000 shapes, 3,600 images, a 40-octave shader, an image pattern, 900 paths and 6,000 texts against react-native-skia 3.8.1 on a Samsung SM-E146B (90 Hz), release builds, mean of two runs. Present FPS is frames that reached the screen.

engine · scenePresent FPSUI FPSJS FPS
tgfx · shapes60.59090
skia · shapes25.89090
tgfx · images90.39090
skia · images66.19090
tgfx · shader15.09090
skia · shader9.39090
tgfx · pattern90.39090
skia · pattern84.89090
tgfx · paths90.39090
skia · paths17.69090
tgfx · text68.39090
skia · text34.49090

Think a number is off? Open an issue.

Prewarm at launch

You can prewarm the GPU device and fonts at launch, so the first <Canvas> mounts faster.

Android

MainApplication.kt
import com.margelo.nitro.tgfx.Tgfx

override fun onCreate() {
  super.onCreate()
  Tgfx.prewarm()
  loadReactNative(this)
}

iOS

Set TgfxPrewarm in Info.plist; no launch code needed.

Info.plist
<key>TgfxPrewarm</key>
<true/>

TGFX_PREWARM=1 or 0 in the scheme overrides the key on local runs. To time it yourself, leave the key out and call prewarm; Swift needs a bridging header.

YourApp-Bridging-Header.h
#import <NitroTgfx/TgfxPrewarm.h>
AppDelegate.swift
TgfxPrewarm.prewarm()

What it buys

Samsung SM-E146B, four cold starts per arm:

without prewarmwith prewarm
libNitroTgfx load10 ms, on the main thread15–19 ms, on the render thread
first canvas window25.3 / 28.4 / 29.0 / 31.0 ms9.6 / 10.9 / 13.5 / 13.5 ms

The first canvas window drops from 28.4 ms to 11.9 ms on average.

Keep it cheap

  • Animate through SharedValue props or NodeHandle.set; changing JSX props re-uploads the scene.

  • Keep children primitive; wrapper components take a slower path.

    {/* fast */}
    <Canvas>{shapes.map((s) => <Rect key={s.id} {...s} />)}</Canvas>
    
    {/* falls back */}
    <Canvas>{shapes.map((s) => <Star key={s.id} {...s} />)}</Canvas>
  • Group same-kind shapes as siblings.

  • Adjacent <Path> siblings with the same solid paint draw as one batch. Keep the path string stable.

  • <Group picture> records a static subtree once. Use <Group layer> only for group opacity or filters.

  • Pool handles with createHandle and toggle visible instead of remounting.

  • For large sprite or mesh collections, use useSpriteBuffer or useVertexBuffer.

  • One animated opacity per chain (Animation).

  • purgeCaches frees the image and GPU caches; getCacheInfo reports them (Images § Cache).

On this page