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

Commit 1a8ef6e

Browse files
Request another frame in ImageReaderSurfaceProducer.dequeueImage if more images are pending in the queue (#55944)
ImageReaderSurfaceProducer will request a frame when an image is enqueued. But there is no guarantee that each request will produce an additional frame. Multiple requests happening within one vsync interval could be merged into one frame. If no other frame is scheduled, then some images will remain in the queue and the image shown on screen will not be the latest image. With this change, ImageReaderSurfaceProducer will continue requesting frames after consuming an image if the queue is not empty. Fixes flutter/flutter#156903 Fixes flutter/flutter#155787
1 parent 179be9b commit 1a8ef6e

File tree

2 files changed

+61
-1
lines changed

2 files changed

+61
-1
lines changed

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

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -553,6 +553,10 @@ boolean canPrune() {
553553
return imageQueue.isEmpty() && lastReaderDequeuedFrom != this;
554554
}
555555

556+
boolean imageQueueIsEmpty() {
557+
return imageQueue.isEmpty();
558+
}
559+
556560
void close() {
557561
closed = true;
558562
if (VERBOSE_LOGS) {
@@ -630,6 +634,7 @@ void onImage(ImageReader reader, Image image) {
630634

631635
PerImage dequeueImage() {
632636
PerImage r = null;
637+
boolean hasPendingImages = false;
633638
synchronized (lock) {
634639
for (PerImageReader reader : imageReaderQueue) {
635640
r = reader.dequeueImage();
@@ -679,6 +684,21 @@ PerImage dequeueImage() {
679684
break;
680685
}
681686
pruneImageReaderQueue();
687+
for (PerImageReader reader : imageReaderQueue) {
688+
if (!reader.imageQueueIsEmpty()) {
689+
hasPendingImages = true;
690+
break;
691+
}
692+
}
693+
}
694+
if (hasPendingImages) {
695+
// Request another frame to ensure that images are consumed until the queue is empty.
696+
handler.post(
697+
() -> {
698+
if (!released) {
699+
scheduleEngineFrame();
700+
}
701+
});
682702
}
683703
return r;
684704
}
@@ -1245,7 +1265,8 @@ private void registerImageTexture(
12451265
flutterJNI.registerImageTexture(textureId, imageTexture);
12461266
}
12471267

1248-
private void scheduleEngineFrame() {
1268+
@VisibleForTesting
1269+
/* package */ void scheduleEngineFrame() {
12491270
flutterJNI.scheduleFrame();
12501271
}
12511272

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

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -826,4 +826,43 @@ public void onSurfaceDestroyed() {}
826826
// Verify.
827827
latch.await();
828828
}
829+
830+
@Test
831+
public void ImageReaderSurfaceProducerSchedulesFrameIfQueueNotEmpty() throws Exception {
832+
FlutterRenderer flutterRenderer = spy(engineRule.getFlutterEngine().getRenderer());
833+
TextureRegistry.SurfaceProducer producer = flutterRenderer.createSurfaceProducer();
834+
FlutterRenderer.ImageReaderSurfaceProducer texture =
835+
(FlutterRenderer.ImageReaderSurfaceProducer) producer;
836+
texture.disableFenceForTest();
837+
texture.setSize(1, 1);
838+
839+
// Render two frames.
840+
for (int i = 0; i < 2; i++) {
841+
Surface surface = texture.getSurface();
842+
assertNotNull(surface);
843+
Canvas canvas = surface.lockHardwareCanvas();
844+
canvas.drawARGB(255, 255, 0, 0);
845+
surface.unlockCanvasAndPost(canvas);
846+
shadowOf(Looper.getMainLooper()).idle();
847+
}
848+
849+
// Each enqueue of an image should result in a call to scheduleEngineFrame.
850+
verify(flutterRenderer, times(2)).scheduleEngineFrame();
851+
852+
// Consume the first image.
853+
Image image = texture.acquireLatestImage();
854+
shadowOf(Looper.getMainLooper()).idle();
855+
856+
// The dequeue should call scheduleEngineFrame because another image
857+
// remains in the queue.
858+
verify(flutterRenderer, times(3)).scheduleEngineFrame();
859+
860+
// Consume the second image.
861+
image = texture.acquireLatestImage();
862+
shadowOf(Looper.getMainLooper()).idle();
863+
864+
// The dequeue should not call scheduleEngineFrame because the queue
865+
// is now empty.
866+
verify(flutterRenderer, times(3)).scheduleEngineFrame();
867+
}
829868
}

0 commit comments

Comments
 (0)