Skip to content
This repository was archived by the owner on Feb 25, 2025. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -511,11 +511,15 @@ public Image acquireLatestImage() {

private void maybeCloseReader(ImageReader reader) {
synchronized (this) {
if (!readersToClose.contains(reader)) {
return;
}
if (this.lastConsumedImage != null && this.lastConsumedImage.reader == reader) {
// There is still a consumed image in flight for this reader. Don't close.
return;
}
if (!readersToClose.contains(reader)) {
if (this.lastProducedImage != null && this.lastProducedImage.reader == reader) {
// There is still a pending image for this reader. Don't close.
return;
}
readersToClose.remove(reader);
Expand Down Expand Up @@ -550,7 +554,7 @@ private void onImage(PerImage image) {
}
// Close the previously pushed buffer.
if (toClose != null) {
Log.i(TAG, "Dropped frame.");
Log.i(TAG, "Dropping rendered frame that was not acquired in time.");
toClose.close();
}
if (image != null) {
Expand Down Expand Up @@ -656,6 +660,11 @@ public void disableFenceForTest() {
// Roboelectric's implementation of SyncFence is borked.
ignoringFence = true;
}

@VisibleForTesting
public int readersToCloseSize() {
return readersToClose.size();
}
}

@Keep
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,12 @@ public class FlutterRendererTest {
private Surface fakeSurface;
private Surface fakeSurface2;

@Before
public void init() {
// Uncomment the following line to enable logging output in test.
// ShadowLog.stream = System.out;
}

@Before
public void setup() {
fakeFlutterJNI = mock(FlutterJNI.class);
Expand Down Expand Up @@ -532,4 +538,65 @@ public void ImageReaderSurfaceProducerSkipsFramesWhenResizeInflight() {
// We should not get a new frame because the produced frame was for the previous size.
assertNull(texture.acquireLatestImage());
}

@Test
public void ImageReaderSurfaceProducerHandlesLateFrameWhenResizeInflight() {
FlutterRenderer flutterRenderer = new FlutterRenderer(fakeFlutterJNI);
FlutterRenderer.ImageReaderSurfaceProducer texture =
flutterRenderer.new ImageReaderSurfaceProducer(0);
texture.disableFenceForTest();

// Returns a null image when one hasn't been produced.
assertNull(texture.acquireLatestImage());

// Give the texture an initial size.
texture.setSize(1, 1);

// Grab the surface so we can render a frame at 1x1 after resizing.
Surface surface = texture.getSurface();
assertNotNull(surface);
Canvas canvas = surface.lockHardwareCanvas();
canvas.drawARGB(255, 255, 0, 0);
surface.unlockCanvasAndPost(canvas);

// Let callbacks run, this will produce a single frame.
shadowOf(Looper.getMainLooper()).idle();

// Resize.
texture.setSize(4, 4);

// Render a frame at the old size (by using the pre-resized Surface)
canvas = surface.lockHardwareCanvas();
canvas.drawARGB(255, 255, 0, 0);
surface.unlockCanvasAndPost(canvas);

// Let callbacks run.
shadowOf(Looper.getMainLooper()).idle();

// We will acquire a frame that is the old size.
Image produced = texture.acquireLatestImage();
assertNotNull(produced);
assertEquals(produced.getWidth(), 1);
assertEquals(produced.getHeight(), 1);

// We will still have one pending reader to be closed.
// If we didn't we would have closed the reader that owned the image we just acquired.
assertEquals(1, texture.readersToCloseSize());

// Render a new frame with the current size.
surface = texture.getSurface();
assertNotNull(surface);
canvas = surface.lockHardwareCanvas();
canvas.drawARGB(255, 255, 0, 0);
surface.unlockCanvasAndPost(canvas);

// Let callbacks run.
shadowOf(Looper.getMainLooper()).idle();

// Acquire the new image.
assertNotNull(texture.acquireLatestImage());

// We will have no pending readers to close.
assertEquals(0, texture.readersToCloseSize());
}
}