diff --git a/shell/platform/android/io/flutter/embedding/engine/renderer/SurfaceTextureWrapper.java b/shell/platform/android/io/flutter/embedding/engine/renderer/SurfaceTextureWrapper.java index b9a9c57d95de4..55f183c6c6cd6 100644 --- a/shell/platform/android/io/flutter/embedding/engine/renderer/SurfaceTextureWrapper.java +++ b/shell/platform/android/io/flutter/embedding/engine/renderer/SurfaceTextureWrapper.java @@ -19,6 +19,7 @@ public class SurfaceTextureWrapper { private SurfaceTexture surfaceTexture; private boolean released; + private boolean attached; public SurfaceTextureWrapper(@NonNull SurfaceTexture surfaceTexture) { this.surfaceTexture = surfaceTexture; @@ -45,6 +46,7 @@ public void release() { if (!released) { surfaceTexture.release(); released = true; + attached = false; } } } @@ -53,16 +55,33 @@ public void release() { @SuppressWarnings("unused") public void attachToGLContext(int texName) { synchronized (this) { - if (!released) { - surfaceTexture.attachToGLContext(texName); + if (released) { + return; + } + // When the rasterizer tasks run on a different thread, the GrContext is re-created. + // This causes the texture to be in an uninitialized state. + // This should *not* be an issue once platform views are always rendered as TextureLayers + // since thread merging will be always disabled on Android. + // For more see: AndroidExternalTextureGL::OnGrContextCreated in + // android_external_texture_gl.cc, and + // https://github.com/flutter/flutter/issues/98155 + if (attached) { + surfaceTexture.detachFromGLContext(); } + surfaceTexture.attachToGLContext(texName); + attached = true; } } // Called by native. @SuppressWarnings("unused") public void detachFromGLContext() { - surfaceTexture.detachFromGLContext(); + synchronized (this) { + if (attached && !released) { + surfaceTexture.detachFromGLContext(); + attached = false; + } + } } // Called by native. diff --git a/shell/platform/android/test/io/flutter/embedding/engine/renderer/SurfaceTextureWrapperTest.java b/shell/platform/android/test/io/flutter/embedding/engine/renderer/SurfaceTextureWrapperTest.java new file mode 100644 index 0000000000000..a9395f675235a --- /dev/null +++ b/shell/platform/android/test/io/flutter/embedding/engine/renderer/SurfaceTextureWrapperTest.java @@ -0,0 +1,82 @@ +package io.flutter.embedding.engine.renderer; + +import static junit.framework.TestCase.*; +import static org.mockito.Mockito.*; + +import android.graphics.SurfaceTexture; +import androidx.test.ext.junit.runners.AndroidJUnit4; +import org.junit.Test; +import org.junit.runner.RunWith; + +@RunWith(AndroidJUnit4.class) +public class SurfaceTextureWrapperTest { + + @Test + public void attachToGLContext() { + final SurfaceTexture tx = mock(SurfaceTexture.class); + final SurfaceTextureWrapper wrapper = new SurfaceTextureWrapper(tx); + + wrapper.attachToGLContext(0); + verify(tx, times(1)).attachToGLContext(0); + verifyNoMoreInteractions(tx); + } + + @Test + public void attachToGLContext_detachesFromCurrentContext() { + final SurfaceTexture tx = mock(SurfaceTexture.class); + final SurfaceTextureWrapper wrapper = new SurfaceTextureWrapper(tx); + + wrapper.attachToGLContext(0); + + reset(tx); + + wrapper.attachToGLContext(0); + verify(tx, times(1)).detachFromGLContext(); + verify(tx, times(1)).attachToGLContext(0); + verifyNoMoreInteractions(tx); + } + + @Test + public void attachToGLContext_doesNotDetacheFromCurrentContext() { + final SurfaceTexture tx = mock(SurfaceTexture.class); + final SurfaceTextureWrapper wrapper = new SurfaceTextureWrapper(tx); + + wrapper.attachToGLContext(0); + + wrapper.detachFromGLContext(); + + reset(tx); + + wrapper.attachToGLContext(0); + verify(tx, times(1)).attachToGLContext(0); + verifyNoMoreInteractions(tx); + } + + @Test + public void detachFromGLContext() { + final SurfaceTexture tx = mock(SurfaceTexture.class); + final SurfaceTextureWrapper wrapper = new SurfaceTextureWrapper(tx); + + wrapper.attachToGLContext(0); + reset(tx); + + wrapper.detachFromGLContext(); + verify(tx, times(1)).detachFromGLContext(); + verifyNoMoreInteractions(tx); + } + + @Test + public void release() { + final SurfaceTexture tx = mock(SurfaceTexture.class); + final SurfaceTextureWrapper wrapper = new SurfaceTextureWrapper(tx); + + wrapper.release(); + + verify(tx, times(1)).release(); + reset(tx); + + wrapper.detachFromGLContext(); + wrapper.attachToGLContext(0); + verifyNoMoreInteractions(tx); + } +}