Skip to content

feat(android): let a transparent canvas opt into a topmost SurfaceView - #457

Open
AlirezaHadjar wants to merge 1 commit into
wcandillon:mainfrom
AlirezaHadjar:android/transparent-surfaceview
Open

feat(android): let a transparent canvas opt into a topmost SurfaceView#457
AlirezaHadjar wants to merge 1 commit into
wcandillon:mainfrom
AlirezaHadjar:android/transparent-surfaceview

Conversation

@AlirezaHadjar

Copy link
Copy Markdown

The problem

A transparent canvas renders through a TextureView, so every frame takes this path:

  1. WebGPU renders into the SurfaceTexture.
  2. HWUI samples that full-screen texture into the React Native window buffer.
  3. SurfaceFlinger composes the window.

Step 2 exists only to get the canvas into the app window, and it forces a window swap on every WebGPU present. On a full-screen canvas animating every vsync it dominates the frame, and it gets worse with canvas area and refresh rate even when the simulation is entirely GPU-resident.

Numbers

Same release build, same animation, same 60 Hz Android emulator, profiled with Perfetto. Only the presentation path changed:

Metric Transparent TextureView Topmost translucent SurfaceView
QueuePresentKHR cadence 45.03 FPS 59.33 FPS
Present intervals over 20 ms 35.0% 2.47%
Present intervals over 33 ms 17.0% 0.45%
RN window swaps during ~15 s of animation 332 0

The patched build also shows the canvas as its own SurfaceView(BLAST) layer in SurfaceFlinger, so this was confirmed at the composition layer, not read off an FPS counter.

This came out of a confetti library that draws a full-screen transparent canvas over app content. 45 FPS with a third of frames blowing the 20 ms budget is the difference between the effect looking smooth and looking cheap.

Why the surface has to be topmost

A plain SurfaceView sits behind the app window, and Android punches a hole in the window so it shows through. Right for opaque video, camera or game surfaces. It cannot do a transparent overlay. In a controlled test, dropping only setZOrderOnTop(true) moved the layer from z=1 to z=-2, the canvas region went black, and the React Native content underneath vanished into the hole. Putting it back returned the layer to z=1 and transparency worked again.

For a transparent canvas there are two workable configurations, TextureView or translucent-and-on-top. That's why this adds one value instead of a general view-type selector. A "plain transparent SurfaceView" mode would just be broken.

The API

<Canvas transparent androidTransparencyMode="surface-overlay" />

"texture" | "surface-overlay", default "texture", ignored when transparent is false. Opaque canvases keep their plain SurfaceView.

Default behavior is unchanged. With the prop absent, or set to "texture", WebGPUView builds a WebGPUTextureView exactly as it does today.

Verification

New example screen under Diagnostics, "Android Transparency Mode": a 50% red canvas over a blue backdrop with a yellow overlay view on top. On an API 36 arm64 emulator:

mode canvas overlay view backing view
prop absent purple pure yellow, on top TextureView
"texture" purple pure yellow, on top TextureView
"surface-overlay" purple orange, seen through the canvas SurfaceView, z-order on top

The overlay turning orange is the z-order proof. The canvas layer is compositing over a view that sits above it in the React Native tree.

Related: #142 (introduced TextureView for transparency and noted the slower animation), #168, #170.

A transparent canvas renders through a TextureView, so HWUI samples the
whole canvas into the app window before SurfaceFlinger composes it. For a
full-screen canvas that animates every vsync that second pass dominates.

androidTransparencyMode="surface-overlay" puts the canvas on its own
translucent SurfaceFlinger layer instead. The tradeoff is z-order: the
layer sits above the app window, so no React Native view can draw over it.
That is why it is opt-in and "texture" stays the default.
@AlirezaHadjar
AlirezaHadjar force-pushed the android/transparent-surfaceview branch from b109de0 to 8c4ab49 Compare August 27, 2026 15:59
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant