Shopify / Shopify/react-native-skia

Android 17: TextureView presents only a fraction of rendered frames (updateTexImage drain from #1980 no longer works); SurfaceView opt-in proposal

Open
#4,058 0 comments 1 reaction 0 assignees View on GitHub

Nobody has claimed this yet.

Dominant language
TypeScript
Stars
8.6k
Forks
647
Avg merge
1d 17h
Merged PRs (30d)
35

Description

Description

On Android 17 the default SkiaTextureView path presents only a fraction of the frames Skia renders, so Reanimated-driven canvases look like a low-fps stepping animation. The same build is smooth on iOS and was smooth on the same device on Android 16.

Cause: the per-frame SurfaceTexture.updateTexImage() drain added in #1980 (the fix for #1960) no longer drains on Android 17. On Android ≤16 it fails after acquiring/releasing the pending buffer (updateAndRelease: EGLConsumer is not attached, #2054), so it still kept the TextureView in step by accident. On 17 it returns early:

E <app>: [SurfaceTexture-0-<pid>-5] updateTexImage: LegacySurfaceTexture is not attached!
I RNSkia  : updateAndRelease() failed. The exception above can safely be ignored

Evidence (Pixel 10, Android 17 build CP2A.260805.005, app render rate 60 Hz):

  • RNSkia log above fires every ~16.6 ms → Skia renders 60 fps.
  • dumpsys gfxinfo <pkg> framestats: HWUI presents the TextureView content in RenderThread-only frames (flag 2) at ~19–23/s, interleaved with main-thread frames every 2 vsyncs; ~44 HWUI frames/s total, intervals alternating 16.6/33.3 ms. GPU time 1 ms p99, so it is not GPU load.
  • Switching the same canvas to SkiaSurfaceView presents every frame and the animation is smooth (verified on the device).
React Native Skia Version

2.11.2

React Native Version

0.87.1 (react-native-reanimated 4.6.0, react-native-worklets 0.12.1)

Using New Architecture
  • Enabled
Steps to Reproduce
  1. Pixel 10 on Android 17.
  2. <Canvas> with a <Path> whose path is a useDerivedValue driven by withSpring shared values (any continuously animated Skia prop works; ours is a 4-corner polygon overlaying a camera preview).
  3. Retarget the springs continuously (or just let one settle) and watch the motion; enable Developer options → "Show refresh rate" to confirm the display is not the limit.
  4. adb logcat | grep -E "RNSkia|SurfaceTexture" and adb shell dumpsys gfxinfo <pkg> framestats while it animates.
Snack, Code Example, Screenshot, or Link to Repository
const x = useSharedValue(0)
useEffect(() => { x.value = withRepeat(withSpring(300), -1, true) }, [])
const path = useDerivedValue(() => {
  const p = Skia.Path.Make()
  p.addRect({ x: x.value, y: 100, width: 120, height: 160 })
  return p
})
<Canvas style={StyleSheet.absoluteFill}>
  <Path path={path} color="lime" style="stroke" strokeWidth={4} />
</Canvas>

Fix we are shipping (patched locally, tested working on a real Pixel 10): use SkiaSurfaceView, made usable as an overlay:

// SkiaSurfaceView constructor
getHolder().setFormat(PixelFormat.TRANSLUCENT); // EGL config already has alpha 8; renderer clears to transparent
setZOrderMediaOverlay(true);                    // above sibling SurfaceViews (camera preview), below the window's views

Today the only way to reach SkiaSurfaceView is opaque, and it is opaque black. Proposal: an opt-in androidSurfaceView prop on Canvas that selects the SurfaceView and applies the translucent format + media-overlay z-order when opaque is false, leaving opaque unchanged. Trade-offs to document: the canvas composites below the window's own views, and usual SurfaceView limitations during view transitions.

Should we submit a PR with this implementation?

Contributor guide

No contributing guide indexed for this repository

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start at the SkiaSurfaceView constructor and the Canvas handling for the proposed androidSurfaceView and existing opaque options; compare this with the current TextureView and updateTexImage path. Confirm the Pixel 10 Android 17 reproduction, then verify that the opt-in SurfaceView works as a translucent media overlay, opaque behavior is unchanged, and the documented SurfaceView limitations are covered.

Written by the indexing model from the issue text.

Assessment

Tech stack
android, java, react-native, typescript
Domain
computer-graphics, mobile
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Active
Clarity
Mostly clear
Newbie friendliness
58/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.