Repository navigation
fix(🤖): open a SurfaceView canvas's hole with its first frame - #561
YevheniiKotyrlo wants to merge 1 commit into
Conversation
Device measurement — before / after
This PR — the stage shows until the first frame, and the hole opens with it.
On the phones, release builds of the example opening the diagnostic at launch, #565's JavaScript (which carries this diagnostic) over Device evidence — before / after, before the rebase onto #574
Galaxy S23 (Android 14), debug build of the example app, the SurfaceView First Frame diagnostic. Each picture is the On that head ( |
046aa39 to
8017549
Compare
A SurfaceView canvas sits behind its window, and the window opens its hole once the SurfaceView reports its draw finished. WebGPUSurfaceView reported it as soon as surfaceChanged returned, so the hole opened on a surface with no frame and showed black until the first one landed. The view now holds the report until a frame is presented into the surface, as GLSurfaceView holds it until its next frame. attachSurface takes a callback for the surface's first present, and each surface carries a token, so a report for a replaced surface is ignored. Before Android 14 the wait is bounded at a second; from Android 14 the platform bounds it. A report that goes out without a frame, at that second or for a later surface of the view, which SurfaceView counts as reported already, no longer opens the hole: the view punches it, and adds it to the window's transparent region, only while its surface has a frame. A context configured before its surface exists, or left by a lost one, no longer presents an empty frame when the next surface attaches, and detach is one operation for a lost surface and a destroyed view. The canvas docs say when the hole opens, the example gains a SurfaceView First Frame diagnostic that can also draw its first frame late, FirstFrameDrawGate is unit tested, and CI runs the Android library's unit tests.
8017549 to
a485b36
Compare


Problem
A
SurfaceViewcanvas on Android sits behind its window, and the window punches a hole where the canvas sits once theSurfaceViewreports its draw finished.WebGPUSurfaceViewimplementsSurfaceHolder.Callback, notCallback2, so that report goes out as soon assurfaceChangedreturns (SurfaceCallbackHelper): the hole opens on a surface with no frame, and black shows through it until the canvas presents.The registry adds black of its own: a context configured before its surface exists creates an empty offscreen texture, and so does
switchToOffscreenwhen a surface goes, and the next surface's attach blits that texture and presents it as a frame.The example's new SurfaceView First Frame diagnostic mounts a
SurfaceViewcanvas over a blue stage and draws its first frame, solid red, half a second after its device is ready, or a second and a half with its late first frame switch. With the late first frame, a remounted canvas onmainis black until that frame, 1546–1565 ms on a Galaxy S23 (Android 14), 1539–1542 ms on a Mi MIX 3 (Android 10) and about 1.5 s on an Android 13 emulator; with this change the stage shows until the first frame on all three (measurement).Solution
WebGPUSurfaceViewimplementsCallback2and holds thedrawingFinishedrunnable ofsurfaceRedrawNeededAsyncuntil a frame is presented into the surface, asGLSurfaceViewholds it until its next frame (FirstFrameDrawGate).attachSurfacetakes a callback for the surface's first present, a frame or the blit of the latest offscreen frame onto it, whichSurfaceInforuns once, on the presenting thread and outside its lock. The frame-presented callback the vote uses skips that blit, and the blit is what a new surface shows first. For aSurfaceViewthe callback reports back to itsWebGPUView, posted to the main thread; that report can arrive after its surface was replaced, so each surface carries a token and a report for another one is ignored.SurfaceSyncGroup'sTRANSACTION_READY_TIMEOUT), so the gate sets no deadline there. A view leaving its window reports the draws it holds: Android 12L settles them itself as the view leaves (android-12.1.0_r27), but Android 13'sSurfaceSyncerhas no timeout and does not.SurfaceViewcounts as reported for good, would still open it on an empty surface:WebGPUSurfaceViewpunches the hole indispatchDraw, and adds itself to the window's transparent region, only whileFirstFrameDrawGate.hasFrame(), and redraws when that changes. Until then the window shows the views behind the canvas.configurewithout a surface no longer creates the empty texture, anddetach, now one operation for a lost surface and a destroyed view (it replacesswitchToOffscreenanddetachSurface), creates none either: the offscreen texture appears with the first frame rendered into it, so a surface that attaches before one is presented nothing. On iOS the same order no longer presents an empty frame to the layer.canvas.mdxsays when the hole opens.This is the
SurfaceViewhalf of wcandillon/react-native-skia#4191 for this repo, with the same gate. Its other half, a hole the canvas does not bound on Android 10 and 11, does not apply here:WebGPUViewclips its children.Tests
FirstFrameDrawGateTestdrives the gate with a recording host: a draw report waits for its own surface's first frame, a frame reported for a replaced surface opens nothing, the one-second bound reports a draw whose frame never comes before Android 14 and no bound applies from it, a detach reports the draws it holds, and the hole stays closed until the surface's own first frame, past the bound and on a later surface. The library had no JVM tests, sobuild.gradleadds JUnit and CI runs:react-native-webgpu:testDebugUnitTestafter the example's Android build. The hole itself shows only in what the window composites, so the numbers above come fromscreenrecord.Verification
yarn lint·yarn tsc·yarn turbo run build --filter='./packages/*'· the Expo template's web build ·yarn test:plugin·yarn check-dawn·yarn build:docs·cpplintwith the package's flags ·clang-formaton the changed lines ·./gradlew :react-native-webgpu:testDebugUnitTest(16 tests) · the example's iOS build for the simulator SDK, on80175493, whose iOS-compiled files this head keeps. CI's Android e2e step (debug build, Metro withCI=true) on an Android 15 emulator: 220 of 222, as onmainc080934e; the two failures, the YUV cases ofExternalTextureColorSpaceandNativeBufferWrapper, fail onmainthere too. On a Galaxy S23 (Android 14) the whole suite passes, 222 of 222;mainc080934epasses 222 of 222. On an iPad Pro 12.9-inch (4th generation, iOS 27.0.1), built and run as CI's iOS job does on a simulator (Debug build, Metro withCI=true), it passed 221 of 221 before the rebase onto #574 (main8f664fca: 221 of 221). Not run here: the SwiftPM example's build and the semantic-release dry run, which needs the repository's token.Known limits
Before Android 12L a
SurfaceViewcanvas leaving the screen can still show one black frame, as onmain: itsSurfaceViewremoves its layer before the window frame that stops punching its hole.A
SurfaceViewthat gets a new surface, scrolled back into view by aremoveClippedSubviewslist or back from the background, shows the views behind the canvas until the canvas presents into it. The canvas presents on its rendering thread after the window has drawn, so a canvas that keeps drawing opens its hole a frame or two later on a Galaxy S23 (Android 14), and one that stopped drawing comes back when #544 presents its last frame.