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

Commit b25d118

Browse files
committed
Hook ImageReaderSurfaceProducer to the onTrimMemory listener interface
- Close all ImageReaders and Images when we get an onTrimMemory callback. - Remove the first frame fix based around caching the last image displayed because it isn't safe to do on some platforms. Leave a TODO to revisit this.
1 parent cb12a8c commit b25d118

File tree

7 files changed

+88
-46
lines changed

7 files changed

+88
-46
lines changed

shell/platform/android/image_external_texture.cc

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,6 @@ void ImageExternalTexture::Paint(PaintContext& context,
2626
if (state_ == AttachmentState::kDetached) {
2727
return;
2828
}
29-
latest_bounds_ = bounds;
3029
Attach(context);
3130
const bool should_process_frame = !freeze;
3231
if (should_process_frame) {

shell/platform/android/image_external_texture.h

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -59,8 +59,6 @@ class ImageExternalTexture : public flutter::Texture {
5959

6060
fml::jni::ScopedJavaGlobalRef<jobject> image_texture_entry_;
6161
std::shared_ptr<PlatformViewAndroidJNI> jni_facade_;
62-
SkRect latest_bounds_ = SkRect::MakeEmpty();
63-
fml::jni::ScopedJavaGlobalRef<jobject> latest_android_image_;
6462

6563
enum class AttachmentState { kUninitialized, kAttached, kDetached };
6664
AttachmentState state_ = AttachmentState::kUninitialized;

shell/platform/android/image_external_texture_gl.cc

Lines changed: 10 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -29,19 +29,19 @@ ImageExternalTextureGL::ImageExternalTextureGL(
2929

3030
void ImageExternalTextureGL::Attach(PaintContext& context) {
3131
if (state_ == AttachmentState::kUninitialized) {
32-
if (!latest_android_image_.is_null() && !latest_bounds_.isEmpty()) {
33-
// After detach the cache of textures will have been cleared. If
34-
// there is an android image we must populate it now so that the
35-
// first frame isn't blank.
36-
JavaLocalRef hardware_buffer = HardwareBufferFor(latest_android_image_);
37-
UpdateImage(hardware_buffer, context);
38-
CloseHardwareBuffer(hardware_buffer);
39-
}
32+
// TODO(johnmccurtchan): We currently display the first frame after an
33+
// attach-detach cycle as blank. There seems to be an issue on some
34+
// devices where ImageReaders/Images from before the detach aren't
35+
// valid after the attach. According to Android folks this doesn't
36+
// match the spec. Revisit this in the future.
37+
// See https://github.com/flutter/flutter/issues/142978 and
38+
// https://github.com/flutter/flutter/issues/139039.
4039
state_ = AttachmentState::kAttached;
4140
}
4241
}
4342

4443
void ImageExternalTextureGL::UpdateImage(JavaLocalRef& hardware_buffer,
44+
const SkRect& bounds,
4545
PaintContext& context) {
4646
AHardwareBuffer* latest_hardware_buffer = AHardwareBufferFor(hardware_buffer);
4747
std::optional<HardwareBufferKey> key =
@@ -57,7 +57,7 @@ void ImageExternalTextureGL::UpdateImage(JavaLocalRef& hardware_buffer,
5757
return;
5858
}
5959

60-
dl_image_ = CreateDlImage(context, latest_bounds_, key, std::move(egl_image));
60+
dl_image_ = CreateDlImage(context, bounds, key, std::move(egl_image));
6161
if (key.has_value()) {
6262
gl_entries_.erase(image_lru_.AddImage(dl_image_, key.value()));
6363
}
@@ -70,17 +70,8 @@ void ImageExternalTextureGL::ProcessFrame(PaintContext& context,
7070
return;
7171
}
7272
JavaLocalRef hardware_buffer = HardwareBufferFor(image);
73-
UpdateImage(hardware_buffer, context);
73+
UpdateImage(hardware_buffer, bounds, context);
7474
CloseHardwareBuffer(hardware_buffer);
75-
76-
// NOTE: In the following code it is important that old_android_image is
77-
// not closed until after the update of egl_image_ otherwise the image might
78-
// be closed before the old EGLImage referencing it has been deleted. After
79-
// an image is closed the underlying HardwareBuffer may be recycled and used
80-
// for a future frame.
81-
JavaLocalRef old_android_image(latest_android_image_);
82-
latest_android_image_.Reset(image);
83-
CloseImage(old_android_image);
8475
}
8576

8677
void ImageExternalTextureGL::Detach() {

shell/platform/android/image_external_texture_gl.h

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,9 @@ class ImageExternalTextureGL : public ImageExternalTexture {
3333
void Attach(PaintContext& context) override;
3434
void Detach() override;
3535
void ProcessFrame(PaintContext& context, const SkRect& bounds) override;
36-
void UpdateImage(JavaLocalRef& hardware_buffer, PaintContext& context);
36+
void UpdateImage(JavaLocalRef& hardware_buffer,
37+
const SkRect& bounds,
38+
PaintContext& context);
3739

3840
virtual sk_sp<flutter::DlImage> CreateDlImage(
3941
PaintContext& context,

shell/platform/android/image_external_texture_vk.cc

Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -38,9 +38,7 @@ void ImageExternalTextureVK::ProcessFrame(PaintContext& context,
3838
if (image.is_null()) {
3939
return;
4040
}
41-
JavaLocalRef old_android_image(latest_android_image_);
42-
latest_android_image_.Reset(image);
43-
JavaLocalRef hardware_buffer = HardwareBufferFor(latest_android_image_);
41+
JavaLocalRef hardware_buffer = HardwareBufferFor(image);
4442
AHardwareBuffer* latest_hardware_buffer = AHardwareBufferFor(hardware_buffer);
4543

4644
AHardwareBuffer_Desc hb_desc = {};
@@ -53,9 +51,6 @@ void ImageExternalTextureVK::ProcessFrame(PaintContext& context,
5351
dl_image_ = existing_image;
5452

5553
CloseHardwareBuffer(hardware_buffer);
56-
// IMPORTANT: We have just received a new frame to display so close the
57-
// previous Java Image so that it is recycled and used for a future frame.
58-
CloseImage(old_android_image);
5954
return;
6055
}
6156

@@ -105,9 +100,6 @@ void ImageExternalTextureVK::ProcessFrame(PaintContext& context,
105100
image_lru_.AddImage(dl_image_, key.value());
106101
}
107102
CloseHardwareBuffer(hardware_buffer);
108-
// IMPORTANT: We have just received a new frame to display so close the
109-
// previous Java Image so that it is recycled and used for a future frame.
110-
CloseImage(old_android_image);
111103
}
112104

113105
} // namespace flutter

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

Lines changed: 40 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -189,6 +189,7 @@ public SurfaceProducer createSurfaceProducer() {
189189
if (!debugForceSurfaceProducerGlTextures && Build.VERSION.SDK_INT >= 29) {
190190
final ImageReaderSurfaceProducer producer = new ImageReaderSurfaceProducer(id);
191191
registerImageTexture(id, producer);
192+
addOnTrimMemoryListener(producer);
192193
Log.v(TAG, "New ImageReaderSurfaceProducer ID: " + id);
193194
entry = producer;
194195
} else {
@@ -404,7 +405,9 @@ public void run() {
404405
@Keep
405406
@TargetApi(29)
406407
final class ImageReaderSurfaceProducer
407-
implements TextureRegistry.SurfaceProducer, TextureRegistry.ImageConsumer {
408+
implements TextureRegistry.SurfaceProducer,
409+
TextureRegistry.ImageConsumer,
410+
TextureRegistry.OnTrimMemoryListener {
408411
private static final String TAG = "ImageReaderSurfaceProducer";
409412
private static final int MAX_IMAGES = 4;
410413

@@ -436,6 +439,7 @@ final class ImageReaderSurfaceProducer
436439
private final LinkedList<PerImageReader> imageReaderQueue = new LinkedList<PerImageReader>();
437440
private final HashMap<ImageReader, PerImageReader> perImageReaders =
438441
new HashMap<ImageReader, PerImageReader>();
442+
private PerImage lastDequeuedImage = null;
439443
private PerImageReader lastReaderDequeuedFrom = null;
440444

441445
/** Internal class: state held per Image produced by ImageReaders. */
@@ -607,7 +611,15 @@ PerImage dequeueImage() {
607611
lastDequeueTime = System.nanoTime();
608612
}
609613
}
610-
// We have dequeued the first image from reader.
614+
if (lastDequeuedImage != null) {
615+
// We must keep the last image dequeued open until we are done presenting it.
616+
// We have just dequeued a new image (r). Close the previously dequeued image.
617+
lastDequeuedImage.image.close();
618+
lastDequeuedImage = null;
619+
}
620+
// Remember the last image and reader dequeued from. We do this because we must
621+
// keep both of these alive until we are done presenting the image.
622+
lastDequeuedImage = r;
611623
lastReaderDequeuedFrom = reader;
612624
break;
613625
}
@@ -616,23 +628,40 @@ PerImage dequeueImage() {
616628
return r;
617629
}
618630

631+
@Override
632+
public void onTrimMemory(int level) {
633+
cleanup();
634+
createNewReader = true;
635+
}
636+
619637
private void releaseInternal() {
638+
cleanup();
620639
released = true;
621-
for (PerImageReader pir : perImageReaders.values()) {
622-
pir.close();
640+
}
641+
642+
private void cleanup() {
643+
synchronized (lock) {
644+
for (PerImageReader pir : perImageReaders.values()) {
645+
pir.close();
646+
}
647+
perImageReaders.clear();
648+
if (lastDequeuedImage != null) {
649+
lastDequeuedImage.image.close();
650+
lastDequeuedImage = null;
651+
}
652+
if (lastReaderDequeuedFrom != null) {
653+
lastReaderDequeuedFrom.close();
654+
lastReaderDequeuedFrom = null;
655+
}
656+
imageReaderQueue.clear();
623657
}
624-
perImageReaders.clear();
625-
imageReaderQueue.clear();
626658
}
627659

628660
@TargetApi(33)
629661
private void waitOnFence(Image image) {
630662
try {
631663
SyncFence fence = image.getFence();
632-
boolean signaled = fence.awaitForever();
633-
if (!signaled) {
634-
Log.e(TAG, "acquireLatestImage image's fence was never signalled.");
635-
}
664+
fence.awaitForever();
636665
} catch (IOException e) {
637666
// Drop.
638667
}
@@ -874,10 +903,7 @@ public void pushImage(Image image) {
874903
private void waitOnFence(Image image) {
875904
try {
876905
SyncFence fence = image.getFence();
877-
boolean signaled = fence.awaitForever();
878-
if (!signaled) {
879-
Log.e(TAG, "acquireLatestImage image's fence was never signalled.");
880-
}
906+
fence.awaitForever();
881907
} catch (IOException e) {
882908
// Drop.
883909
}

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

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -622,6 +622,40 @@ public void ImageReaderSurfaceProducerImageReadersAndImagesCount() {
622622
assertEquals(0, texture.numImages());
623623
}
624624

625+
@Test
626+
public void ImageReaderSurfaceProducerTrimMemoryCallback() {
627+
FlutterRenderer flutterRenderer = new FlutterRenderer(fakeFlutterJNI);
628+
FlutterRenderer.ImageReaderSurfaceProducer texture =
629+
flutterRenderer.new ImageReaderSurfaceProducer(0);
630+
texture.disableFenceForTest();
631+
632+
// Returns a null image when one hasn't been produced.
633+
assertNull(texture.acquireLatestImage());
634+
635+
// Give the texture an initial size.
636+
texture.setSize(1, 1);
637+
638+
// Grab the surface so we can render a frame at 1x1 after resizing.
639+
Surface surface = texture.getSurface();
640+
assertNotNull(surface);
641+
Canvas canvas = surface.lockHardwareCanvas();
642+
canvas.drawARGB(255, 255, 0, 0);
643+
surface.unlockCanvasAndPost(canvas);
644+
645+
// Let callbacks run, this will produce a single frame.
646+
shadowOf(Looper.getMainLooper()).idle();
647+
648+
assertEquals(1, texture.numImageReaders());
649+
assertEquals(1, texture.numImages());
650+
651+
// Invoke the onTrimMemory callback.
652+
texture.onTrimMemory(0);
653+
shadowOf(Looper.getMainLooper()).idle();
654+
655+
assertEquals(0, texture.numImageReaders());
656+
assertEquals(0, texture.numImages());
657+
}
658+
625659
// A 0x0 ImageReader is a runtime error.
626660
@Test
627661
public void ImageReaderSurfaceProducerClampsWidthAndHeightTo1() {

0 commit comments

Comments
 (0)