Skip to content
This repository was archived by the owner on Feb 25, 2025. It is now read-only.

Commit ea6adb0

Browse files
authored
Test the SurfaceTextureSurfaceProducer-branch in the Android scenario_app (#51061)
This PR does the following: - Relands #50993. - Fixes a bug in `SurfaceTextureSurfaceProducer` where it would crash on release (now tested, yay!) Closes flutter/flutter#143539. Closes flutter/flutter#143483. /cc @gaaclarke @chinmaygarde, this PR should (after landed) test/verify #50730.
1 parent 49f5d96 commit ea6adb0

File tree

13 files changed

+195
-62
lines changed

13 files changed

+195
-62
lines changed

ci/builders/linux_android_emulator_opengles.json

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,32 @@
5757
"--enable-impeller",
5858
"--impeller-backend=opengles"
5959
]
60+
},
61+
{
62+
"language": "dart",
63+
"name": "Android Scenario App Integration Tests (Impeller/OpenGLES, SurfaceTexture)",
64+
"test_timeout_secs": 900,
65+
"max_attempts": 2,
66+
"test_dependencies": [
67+
{
68+
"dependency": "android_virtual_device",
69+
"version": "android_34_google_apis_x64.textpb"
70+
},
71+
{
72+
"dependency": "avd_cipd_version",
73+
"version": "build_id:8759428741582061553"
74+
}
75+
],
76+
"contexts": [
77+
"android_virtual_device"
78+
],
79+
"script": "flutter/testing/scenario_app/bin/run_android_tests.dart",
80+
"parameters": [
81+
"--out-dir=../out/android_emulator_opengles_debug_x64",
82+
"--enable-impeller",
83+
"--impeller-backend=opengles",
84+
"--force-surface-producer-surface-texture"
85+
]
6086
}
6187
]
6288
}

ci/builders/linux_android_emulator_skia.json

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,31 @@
5757
"--out-dir=../out/android_emulator_skia_debug_x64",
5858
"--no-enable-impeller"
5959
]
60+
},
61+
{
62+
"language": "dart",
63+
"name": "Android Scenario App Integration Tests (Skia, SurfaceTexture)",
64+
"test_timeout_secs": 900,
65+
"max_attempts": 2,
66+
"test_dependencies": [
67+
{
68+
"dependency": "android_virtual_device",
69+
"version": "android_34_google_apis_x64.textpb"
70+
},
71+
{
72+
"dependency": "avd_cipd_version",
73+
"version": "build_id:8759428741582061553"
74+
}
75+
],
76+
"contexts": [
77+
"android_virtual_device"
78+
],
79+
"script": "flutter/testing/scenario_app/bin/run_android_tests.dart",
80+
"parameters": [
81+
"--out-dir=../out/android_emulator_skia_debug_x64",
82+
"--no-enable-impeller",
83+
"--force-surface-producer-surface-texture"
84+
]
6085
}
6186
]
6287
}

shell/platform/android/io/flutter/embedding/engine/renderer/FlutterRenderer.java

Lines changed: 26 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ public class FlutterRenderer implements TextureRegistry {
5757
* the OpenGLES/{@link SurfaceTextureSurfaceProducer} code branch. This flag has undefined
5858
* behavior if set to true while running in a Vulkan (Impeller) context.
5959
*/
60-
@VisibleForTesting static boolean debugForceSurfaceProducerGlTextures = false;
60+
@VisibleForTesting public static boolean debugForceSurfaceProducerGlTextures = false;
6161

6262
private static final String TAG = "FlutterRenderer";
6363

@@ -184,19 +184,24 @@ public SurfaceProducer createSurfaceProducer() {
184184
// Coincidentally, if ImageTexture is available, we are also on an Android
185185
// version that is
186186
// running Vulkan, so we don't have to worry about it not being supported.
187-
final long id = nextTextureId.getAndIncrement();
188187
final SurfaceProducer entry;
189188
if (!debugForceSurfaceProducerGlTextures && Build.VERSION.SDK_INT >= 29) {
189+
final long id = nextTextureId.getAndIncrement();
190190
final ImageReaderSurfaceProducer producer = new ImageReaderSurfaceProducer(id);
191191
registerImageTexture(id, producer);
192192
addOnTrimMemoryListener(producer);
193193
Log.v(TAG, "New ImageReaderSurfaceProducer ID: " + id);
194194
entry = producer;
195195
} else {
196+
// TODO(matanlurey): Actually have the class named "*Producer" to well, produce
197+
// something. This is a code smell, but does guarantee the paths for both
198+
// createSurfaceTexture and createSurfaceProducer doesn't diverge. As we get more
199+
// confident in this API and any possible bugs (and have tests to check we don't
200+
// regress), reconsider this pattern.
201+
final SurfaceTextureEntry texture = createSurfaceTexture();
196202
final SurfaceTextureSurfaceProducer producer =
197-
new SurfaceTextureSurfaceProducer(id, handler, flutterJNI);
198-
registerSurfaceTexture(id, producer.getSurfaceTexture());
199-
Log.v(TAG, "New SurfaceTextureSurfaceProducer ID: " + id);
203+
new SurfaceTextureSurfaceProducer(texture.id(), handler, flutterJNI, texture);
204+
Log.v(TAG, "New SurfaceTextureSurfaceProducer ID: " + texture.id());
200205
entry = producer;
201206
}
202207
return entry;
@@ -630,8 +635,9 @@ PerImage dequeueImage() {
630635
+ " closing image="
631636
+ lastDequeuedImage.image.hashCode());
632637
}
633-
// We must keep the last image dequeued open until we are done presenting it.
634-
// We have just dequeued a new image (r). Close the previously dequeued image.
638+
// We must keep the last image dequeued open until we are done presenting
639+
// it. We have just dequeued a new image (r). Close the previously dequeued
640+
// image.
635641
lastDequeuedImage.image.close();
636642
lastDequeuedImage = null;
637643
}
@@ -817,11 +823,11 @@ private ImageReader createImageReader33() {
817823
// Allow for double buffering.
818824
builder.setMaxImages(MAX_IMAGES);
819825
// Use PRIVATE image format so that we can support video decoding.
820-
// TODO(johnmccutchan): Should we always use PRIVATE here? It may impact our ability to read
821-
// back texture data. If we don't always want to use it, how do we decide when to use it or
822-
// not? Perhaps PlatformViews can indicate if they may contain DRM'd content. I need to
823-
// investigate how PRIVATE impacts our ability to take screenshots or capture the output of
824-
// Flutter application.
826+
// TODO(johnmccutchan): Should we always use PRIVATE here? It may impact our ability to
827+
// read back texture data. If we don't always want to use it, how do we decide when to
828+
// use it or not? Perhaps PlatformViews can indicate if they may contain DRM'd content.
829+
// I need to investigate how PRIVATE impacts our ability to take screenshots or capture
830+
// the output of Flutter application.
825831
builder.setImageFormat(ImageFormat.PRIVATE);
826832
// Hint that consumed images will only be read by GPU.
827833
builder.setUsage(HardwareBuffer.USAGE_GPU_SAMPLED_IMAGE);
@@ -1008,10 +1014,10 @@ protected void finalize() throws Throwable {
10081014
*/
10091015
public void startRenderingToSurface(@NonNull Surface surface, boolean onlySwap) {
10101016
if (!onlySwap) {
1011-
// Stop rendering to the surface releases the associated native resources, which causes a
1012-
// glitch when toggling between rendering to an image view (hybrid composition) and rendering
1013-
// directly to a Surface or Texture view.
1014-
// For more, https://github.com/flutter/flutter/issues/95343
1017+
// Stop rendering to the surface releases the associated native resources, which causes
1018+
// a glitch when toggling between rendering to an image view (hybrid composition) and
1019+
// rendering directly to a Surface or Texture view. For more,
1020+
// https://github.com/flutter/flutter/issues/95343
10151021
stopRenderingToSurface();
10161022
}
10171023

@@ -1062,10 +1068,10 @@ public void stopRenderingToSurface() {
10621068
if (surface != null) {
10631069
flutterJNI.onSurfaceDestroyed();
10641070

1065-
// TODO(mattcarroll): the source of truth for this call should be FlutterJNI, which is where
1066-
// the call to onFlutterUiDisplayed() comes from. However, no such native callback exists yet,
1067-
// so until the engine and FlutterJNI are configured to call us back when rendering stops, we
1068-
// will manually monitor that change here.
1071+
// TODO(mattcarroll): the source of truth for this call should be FlutterJNI, which is
1072+
// where the call to onFlutterUiDisplayed() comes from. However, no such native callback
1073+
// exists yet, so until the engine and FlutterJNI are configured to call us back when
1074+
// rendering stops, we will manually monitor that change here.
10691075
if (isDisplayingFlutterUi) {
10701076
flutterUiDisplayListener.onFlutterUiNoLongerDisplayed();
10711077
}

shell/platform/android/io/flutter/embedding/engine/renderer/SurfaceTextureSurfaceProducer.java

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,15 +16,19 @@ final class SurfaceTextureSurfaceProducer
1616
private int requestedBufferHeight;
1717
private boolean released;
1818
@Nullable private Surface surface;
19-
@NonNull private final SurfaceTexture texture;
19+
@NonNull private final TextureRegistry.SurfaceTextureEntry texture;
2020
@NonNull private final Handler handler;
2121
@NonNull private final FlutterJNI flutterJNI;
2222

23-
SurfaceTextureSurfaceProducer(long id, @NonNull Handler handler, @NonNull FlutterJNI flutterJNI) {
23+
SurfaceTextureSurfaceProducer(
24+
long id,
25+
@NonNull Handler handler,
26+
@NonNull FlutterJNI flutterJNI,
27+
@NonNull TextureRegistry.SurfaceTextureEntry texture) {
2428
this.id = id;
2529
this.handler = handler;
2630
this.flutterJNI = flutterJNI;
27-
this.texture = new SurfaceTexture(0);
31+
this.texture = texture;
2832
}
2933

3034
@Override
@@ -54,7 +58,7 @@ public void release() {
5458
@Override
5559
@NonNull
5660
public SurfaceTexture getSurfaceTexture() {
57-
return texture;
61+
return texture.surfaceTexture();
5862
}
5963

6064
@Override
@@ -77,7 +81,7 @@ public int getHeight() {
7781
@Override
7882
public Surface getSurface() {
7983
if (surface == null) {
80-
surface = new Surface(texture);
84+
surface = new Surface(texture.surfaceTexture());
8185
}
8286
return surface;
8387
}

shell/platform/android/io/flutter/view/FlutterView.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -968,6 +968,7 @@ public SurfaceTextureWrapper textureWrapper() {
968968
return textureWrapper;
969969
}
970970

971+
@NonNull
971972
@Override
972973
public SurfaceTexture surfaceTexture() {
973974
return textureWrapper.surfaceTexture();

shell/platform/android/test/io/flutter/embedding/engine/renderer/SurfaceTextureSurfaceProducerTest.java

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77
import android.annotation.TargetApi;
88
import android.graphics.Canvas;
9+
import android.graphics.SurfaceTexture;
910
import android.os.Handler;
1011
import android.os.Looper;
1112
import android.view.Surface;
@@ -22,10 +23,13 @@ public final class SurfaceTextureSurfaceProducerTest {
2223

2324
@Test
2425
public void createsSurfaceTextureOfGivenSizeAndResizesWhenRequested() {
26+
final FlutterRenderer flutterRenderer = new FlutterRenderer(fakeJNI);
27+
2528
// Create a surface and set the initial size.
2629
final Handler handler = new Handler(Looper.getMainLooper());
2730
final SurfaceTextureSurfaceProducer producer =
28-
new SurfaceTextureSurfaceProducer(0, handler, fakeJNI);
31+
new SurfaceTextureSurfaceProducer(
32+
0, handler, fakeJNI, flutterRenderer.registerSurfaceTexture(new SurfaceTexture(0)));
2933
final Surface surface = producer.getSurface();
3034
AtomicInteger frames = new AtomicInteger();
3135
producer

shell/platform/android/test/io/flutter/plugin/platform/PlatformViewsControllerTest.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
import android.view.ViewParent;
2727
import android.widget.FrameLayout;
2828
import android.widget.FrameLayout.LayoutParams;
29+
import androidx.annotation.NonNull;
2930
import androidx.test.core.app.ApplicationProvider;
3031
import androidx.test.ext.junit.runners.AndroidJUnit4;
3132
import io.flutter.embedding.android.FlutterImageView;
@@ -1555,6 +1556,7 @@ public SurfaceTextureEntry createSurfaceTexture() {
15551556
@Override
15561557
public SurfaceTextureEntry registerSurfaceTexture(SurfaceTexture surfaceTexture) {
15571558
return new SurfaceTextureEntry() {
1559+
@NonNull
15581560
@Override
15591561
public SurfaceTexture surfaceTexture() {
15601562
return mock(SurfaceTexture.class);

testing/scenario_app/android/README.md

Lines changed: 45 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,13 @@ Or for a specific, build, such as `android_debug_unopt_arm64`:
1717
dart ./testing/scenario_app/bin/run_android_tests.dart --out-dir=../out/android_debug_unopt_arm64
1818
```
1919

20+
See also:
21+
22+
- [File an issue][file_issue] with the `e: scenario-app, platform-android`
23+
labels.
24+
25+
[file_issue]: https://github.com/flutter/flutter/issues/new?labels=e:%20scenario-app,engine,platform-android,fyi-android,team-engine
26+
2027
## Debugging
2128

2229
Debugging the tests on CI is not straightforward but is being improved:
@@ -38,31 +45,51 @@ or locally in `out/.../scenario_app/logs`.
3845

3946
You can then view the logs and screenshots on LUCI. [For example](https://ci.chromium.org/ui/p/flutter/builders/try/Linux%20Engine%20Drone/2003164/overview):
4047

41-
![Screenshot of the Logs on LUCI](https://github.com/flutter/engine/assets/168174/79dc864c-c18b-4df9-a733-fd55301cc69c).
48+
![Screenshot of the Logs on LUCI](https://github.com/flutter/engine/assets/168174/79dc864c-c18b-4df9-a733-fd55301cc69c)
49+
50+
For a full list of flags, see [the runner](../bin/README.md).
4251

4352
## CI Configuration
4453

45-
See [`ci/builders/linux_android_emulator.json`](../../../ci/builders/linux_android_emulator.json)
46-
, and grep for `run_android_tests.dart`.
54+
See [`ci/builders`](../../../ci/builders) and grep for `run_android_tests.dart`.
55+
56+
### Skia
57+
58+
> [!NOTE]
59+
> As of 2024-02-28, Flutter on Android defaults to the Skia graphics backend.
60+
61+
There are two code branches we test using `scenario_app`:
62+
63+
- Older Android devices, that use `SurfaceTexture`.
64+
- CI Configuration (TODO: Link)
65+
- CI History (TODO: Link)
66+
- Skia Gold (TODO: Link)
67+
- Newer Android devices, (API 34) that use `ImageReader`.
68+
- CI Configuration (TODO: Link)
69+
- CI History (TODO: Link)
70+
- Skia Gold (TODO: Link)
71+
72+
### Impeller with OpenGLES
73+
74+
There are two code branches we test using `scenario_app`:
4775

48-
The following matrix of configurations is tested on the CI:
76+
- Older Android devices, that use `SurfaceTexture`.
77+
- CI Configuration (TODO: Link)
78+
- CI History (TODO: Link)
79+
- Skia Gold (TODO: Link)
80+
- Newer Android devices, (API 34) that use `ImageReader`.
81+
- CI Configuration (TODO: Link)
82+
- CI History (TODO: Link)
83+
- Skia Gold (TODO: Link)
4984

50-
<!-- TODO(matanlurey): Blocked by https://github.com/flutter/flutter/issues/143471.
51-
| 28 | Skia | [Android 28 + Skia][skia-gold-skia-28] | Older Android devices (without `ImageReader`) on Skia. |
52-
| 28 | Impeller (OpenGLES) | [Android 28 + Impeller OpenGLES][skia-gold-impeller-opengles-28] | Older Android devices (without `ImageReader`) on Impeller. |
53-
[skia-gold-skia-28]: https://flutter-engine-gold.skia.org/search?left_filter=AndroidAPILevel%3D28%26GraphicsBackend%3Dskia&negative=true&positive=true&right_filter=AndroidAPILevel%3D28%26GraphicsBackend%3Dskia
54-
[skia-gold-impeller-opengles-28]: https://flutter-engine-gold.skia.org/search?left_filter=AndroidAPILevel%3D28%26GraphicsBackend%3Dimpeller-opengles&negative=true&positive=true&right_filter=AndroidAPILevel%3D28%26GraphicsBackend%3Dimpeller-opengles
55-
-->
85+
### Impeller with Vulkan
5686

57-
| API Version | Graphics Backend | Skia Gold | Rationale |
58-
| ----------- | ------------------- | ---------------------------------------------------------------- | ------------------------------------------------ |
59-
| 34 | Skia | [Android 34 + Skia][skia-gold-skia-34] | Newer Android devices on Skia. |
60-
| 34 | Impeller (OpenGLES) | [Android 34 + Impeller OpenGLES][skia-gold-impeller-opengles-34] | Newer Android devices on Impeller with OpenGLES. |
61-
| 34 | Impeller (Vulkan) | [Android 34 + Impeller Vulkan][skia-gold-impeller-vulkan-34] | Newer Android devices on Impeller. |
87+
There is only a single code branch we test using `scenario_app`:
6288

63-
[skia-gold-skia-34]: https://flutter-engine-gold.skia.org/search?left_filter=AndroidAPILevel%3D34%26GraphicsBackend%3Dskia&negative=true&positive=true&right_filter=AndroidAPILevel%3D34%26GraphicsBackend%3Dskia
64-
[skia-gold-impeller-opengles-34]: https://flutter-engine-gold.skia.org/search?left_filter=AndroidAPILevel%3D34%26GraphicsBackend%3Dimpeller-opengles&negative=true&positive=true&right_filter=AndroidAPILevel%3D34%26GraphicsBackend%3Dimpeller-opengles
65-
[skia-gold-impeller-vulkan-34]: https://flutter-engine-gold.skia.org/search?left_filter=AndroidAPILevel%3D34%26GraphicsBackend%3Dimpeller-vulkan&negative=true&positive=true&right_filter=AndroidAPILevel%3D34%26GraphicsBackend%3Dimpeller-vulkan
89+
- Newer Android devices, (API 34)
90+
- CI Configuration (TODO: Link)
91+
- CI History (TODO: Link)
92+
- Skia Gold (TODO: Link)
6693

6794
## Updating Gradle dependencies
6895

testing/scenario_app/android/app/src/androidTest/java/dev/flutter/TestRunner.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,13 @@
99
import androidx.test.runner.AndroidJUnitRunner;
1010
import dev.flutter.scenariosui.ScreenshotUtil;
1111
import io.flutter.FlutterInjector;
12+
import io.flutter.embedding.engine.renderer.FlutterRenderer;
1213

1314
public class TestRunner extends AndroidJUnitRunner {
1415
@Override
1516
public void onCreate(@Nullable Bundle arguments) {
1617
String[] engineArguments = null;
18+
assert arguments != null;
1719
if ("true".equals(arguments.getString("enable-impeller"))) {
1820
// Set up the global settings object so that Impeller is enabled for all tests.
1921
engineArguments =
@@ -22,6 +24,10 @@ public void onCreate(@Nullable Bundle arguments) {
2224
"--impeller-backend=" + arguments.getString("impeller-backend", "vulkan")
2325
};
2426
}
27+
if ("true".equals(arguments.getString("force-surface-producer-surface-texture"))) {
28+
// Set a test flag to force the SurfaceProducer to use SurfaceTexture.
29+
FlutterRenderer.debugForceSurfaceProducerGlTextures = true;
30+
}
2531
// For consistency, just always initilaize FlutterJNI etc.
2632
FlutterInjector.instance().flutterLoader().startInitialization(getTargetContext());
2733
FlutterInjector.instance()

0 commit comments

Comments
 (0)