From 8c4ab4903db2baaf41765e4796875d3cad693780 Mon Sep 17 00:00:00 2001 From: Alireza Hadjar <57192409+AlirezaHadjar@users.noreply.github.com> Date: Thu, 27 Aug 2026 17:59:36 +0200 Subject: [PATCH] =?UTF-8?q?feat(=F0=9F=A4=96):=20let=20a=20transparent=20c?= =?UTF-8?q?anvas=20opt=20into=20a=20topmost=20SurfaceView?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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. --- apps/example/src/App.tsx | 2 + .../src/Diagnostics/DiagnosticsList.tsx | 4 + .../src/Diagnostics/TransparencyMode.tsx | 112 ++++++++++++++++++ apps/example/src/Route.ts | 1 + .../java/com/webgpu/WebGPUSurfaceView.java | 11 ++ .../src/main/java/com/webgpu/WebGPUView.java | 45 ++++--- .../java/com/webgpu/WebGPUViewManager.java | 6 + packages/webgpu/src/Canvas.tsx | 14 ++- .../webgpu/src/WebGPUViewNativeComponent.ts | 9 +- .../src/WebGPUViewNativeComponent.web.ts | 9 +- 10 files changed, 195 insertions(+), 18 deletions(-) create mode 100644 apps/example/src/Diagnostics/TransparencyMode.tsx diff --git a/apps/example/src/App.tsx b/apps/example/src/App.tsx index 821b30d6ef..6071fac009 100644 --- a/apps/example/src/App.tsx +++ b/apps/example/src/App.tsx @@ -43,6 +43,7 @@ import { ViewFormatsUseAfterFree } from "./Diagnostics/ViewFormatsUseAfterFree"; import { RenderAfterUnmount } from "./Diagnostics/RenderAfterUnmount"; import { BackgroundDetach } from "./Diagnostics/BackgroundDetach"; import { SurfaceChurn } from "./Diagnostics/SurfaceChurn"; +import { TransparencyMode } from "./Diagnostics/TransparencyMode"; import { StorageBufferVertices } from "./StorageBufferVertices"; import { SharedTextureMemory } from "./SharedTextureMemory"; import { ImportExternalTexture } from "./ImportExternalTexture"; @@ -126,6 +127,7 @@ function App() { /> + { diff --git a/apps/example/src/Diagnostics/TransparencyMode.tsx b/apps/example/src/Diagnostics/TransparencyMode.tsx new file mode 100644 index 0000000000..034dbad104 --- /dev/null +++ b/apps/example/src/Diagnostics/TransparencyMode.tsx @@ -0,0 +1,112 @@ +import React, { useEffect, useRef, useState } from "react"; +import { Button, Pressable, StyleSheet, Text, View } from "react-native"; +import type { CanvasRef } from "react-native-webgpu"; +import { Canvas } from "react-native-webgpu"; + +type Mode = "default" | "texture" | "surface-overlay"; + +const ClearCanvas = ({ mode }: { mode: Mode }) => { + const ref = useRef(null); + + useEffect(() => { + let live = true; + (async () => { + const adapter = await navigator.gpu.requestAdapter(); + const device = await adapter!.requestDevice(); + const context = ref.current?.getContext("webgpu"); + if (!context || !live) { + return; + } + context.configure({ + device, + format: navigator.gpu.getPreferredCanvasFormat(), + alphaMode: "premultiplied", + }); + const frame = () => { + if (!live) { + return; + } + const encoder = device.createCommandEncoder(); + const pass = encoder.beginRenderPass({ + colorAttachments: [ + { + view: context.getCurrentTexture().createView(), + clearValue: [0.5, 0, 0, 0.5], + loadOp: "clear", + storeOp: "store", + }, + ], + }); + pass.end(); + device.queue.submit([encoder.finish()]); + context.present(); + setTimeout(frame, 200); + }; + frame(); + })().catch((error) => { + console.error(`[transparency] setup failed: ${error}`); + }); + return () => { + live = false; + }; + }, [mode]); + + return ( + + ); +}; + +export const TransparencyMode = () => { + const [mode, setMode] = useState("default"); + const [taps, setTaps] = useState(0); + + return ( + + + Half-transparent red canvas over a blue backdrop. In "texture" + mode the yellow overlay below stays visible on top of the canvas. In + "surface-overlay" mode the canvas gets its own layer and + covers the overlay. + + +