Flutter Engine
The Flutter Engine
FlutterRenderer.java
Go to the documentation of this file.
1// Copyright 2013 The Flutter Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5package io.flutter.embedding.engine.renderer;
6
7import static io.flutter.Build.API_LEVELS;
8
9import android.annotation.TargetApi;
10import android.content.ComponentCallbacks2;
11import android.graphics.Bitmap;
12import android.graphics.ImageFormat;
13import android.graphics.Rect;
14import android.graphics.SurfaceTexture;
15import android.hardware.HardwareBuffer;
16import android.hardware.SyncFence;
17import android.media.Image;
18import android.media.ImageReader;
19import android.os.Build;
20import android.os.Handler;
21import android.os.Looper;
22import android.view.Surface;
23import androidx.annotation.Keep;
24import androidx.annotation.NonNull;
25import androidx.annotation.Nullable;
26import androidx.annotation.VisibleForTesting;
27import io.flutter.Log;
28import io.flutter.embedding.engine.FlutterJNI;
29import io.flutter.view.TextureRegistry;
30import java.io.IOException;
31import java.lang.ref.WeakReference;
32import java.nio.ByteBuffer;
33import java.util.ArrayDeque;
34import java.util.ArrayList;
35import java.util.HashMap;
36import java.util.HashSet;
37import java.util.Iterator;
38import java.util.List;
39import java.util.Set;
40import java.util.concurrent.atomic.AtomicLong;
41
42/**
43 * Represents the rendering responsibilities of a {@code FlutterEngine}.
44 *
45 * <p>{@code FlutterRenderer} works in tandem with a provided {@link RenderSurface} to paint Flutter
46 * pixels to an Android {@code View} hierarchy.
47 *
48 * <p>{@code FlutterRenderer} manages textures for rendering, and forwards some Java calls to native
49 * Flutter code via JNI. The corresponding {@link RenderSurface} provides the Android {@link
50 * Surface} that this renderer paints.
51 *
52 * <p>{@link io.flutter.embedding.android.FlutterSurfaceView} and {@link
53 * io.flutter.embedding.android.FlutterTextureView} are implementations of {@link RenderSurface}.
54 */
55public class FlutterRenderer implements TextureRegistry {
56 /**
57 * Whether to always use GL textures for {@link FlutterRenderer#createSurfaceProducer()}.
58 *
59 * <p>This is a debug-only API intended for local development. For example, when using a newer
60 * Android device (that normally would use {@link ImageReaderSurfaceProducer}, but wanting to test
61 * the OpenGLES/{@link SurfaceTextureSurfaceProducer} code branch. This flag has undefined
62 * behavior if set to true while running in a Vulkan (Impeller) context.
63 */
64 @VisibleForTesting public static boolean debugForceSurfaceProducerGlTextures = false;
65
66 /** Whether to disable clearing of the Surface used to render platform views. */
67 @VisibleForTesting public static boolean debugDisableSurfaceClear = false;
68
69 private static final String TAG = "FlutterRenderer";
70
71 @NonNull private final FlutterJNI flutterJNI;
72 @NonNull private final AtomicLong nextTextureId = new AtomicLong(0L);
73 @Nullable private Surface surface;
74 private boolean isDisplayingFlutterUi = false;
75 private final Handler handler = new Handler();
76
77 @NonNull
78 private final Set<WeakReference<TextureRegistry.OnTrimMemoryListener>> onTrimMemoryListeners =
79 new HashSet<>();
80
81 @NonNull
82 private final FlutterUiDisplayListener flutterUiDisplayListener =
84 @Override
85 public void onFlutterUiDisplayed() {
86 isDisplayingFlutterUi = true;
87 }
88
89 @Override
90 public void onFlutterUiNoLongerDisplayed() {
91 isDisplayingFlutterUi = false;
92 }
93 };
94
95 public FlutterRenderer(@NonNull FlutterJNI flutterJNI) {
96 this.flutterJNI = flutterJNI;
97 this.flutterJNI.addIsDisplayingFlutterUiListener(flutterUiDisplayListener);
98 }
99
100 /**
101 * Returns true if this {@code FlutterRenderer} is painting pixels to an Android {@code View}
102 * hierarchy, false otherwise.
103 */
104 public boolean isDisplayingFlutterUi() {
105 return isDisplayingFlutterUi;
106 }
107
108 /**
109 * Adds a listener that is invoked whenever this {@code FlutterRenderer} starts and stops painting
110 * pixels to an Android {@code View} hierarchy.
111 */
113 flutterJNI.addIsDisplayingFlutterUiListener(listener);
114
115 if (isDisplayingFlutterUi) {
116 listener.onFlutterUiDisplayed();
117 }
118 }
119
120 /**
121 * Removes a listener added via {@link
122 * #addIsDisplayingFlutterUiListener(FlutterUiDisplayListener)}.
123 */
125 flutterJNI.removeIsDisplayingFlutterUiListener(listener);
126 }
127
128 private void clearDeadListeners() {
129 final Iterator<WeakReference<OnTrimMemoryListener>> iterator = onTrimMemoryListeners.iterator();
130 while (iterator.hasNext()) {
131 WeakReference<OnTrimMemoryListener> listenerRef = iterator.next();
132 final OnTrimMemoryListener listener = listenerRef.get();
133 if (listener == null) {
134 iterator.remove();
135 }
136 }
137 }
138
139 /** Adds a listener that is invoked when a memory pressure warning was forward. */
140 @VisibleForTesting
141 /* package */ void addOnTrimMemoryListener(@NonNull OnTrimMemoryListener listener) {
142 // Purge dead listener to avoid accumulating.
143 clearDeadListeners();
144 onTrimMemoryListeners.add(new WeakReference<>(listener));
145 }
146
147 /**
148 * Removes a {@link OnTrimMemoryListener} that was added with {@link
149 * #addOnTrimMemoryListener(OnTrimMemoryListener)}.
150 */
151 @VisibleForTesting
152 /* package */ void removeOnTrimMemoryListener(@NonNull OnTrimMemoryListener listener) {
153 for (WeakReference<OnTrimMemoryListener> listenerRef : onTrimMemoryListeners) {
154 if (listenerRef.get() == listener) {
155 onTrimMemoryListeners.remove(listenerRef);
156 break;
157 }
158 }
159 }
160
161 // ------ START TextureRegistry IMPLEMENTATION -----
162
163 /**
164 * Creates and returns a new external texture {@link SurfaceProducer} managed by the Flutter
165 * engine that is also made available to Flutter code.
166 */
167 @NonNull
168 @Override
170 // Prior to Impeller, Flutter on Android *only* ran on OpenGLES (via Skia). That
171 // meant that
172 // plugins (i.e. end-users) either explicitly created a SurfaceTexture (via
173 // createX/registerX) or an ImageTexture (via createX/registerX).
174 //
175 // In an Impeller world, which for the first time uses (if available) a Vulkan
176 // rendering
177 // backend, it is no longer possible (at least not trivially) to render an
178 // OpenGLES-provided
179 // texture (SurfaceTexture) in a Vulkan context.
180 //
181 // This function picks the "best" rendering surface based on the Android
182 // runtime, and
183 // provides a consumer-agnostic SurfaceProducer (which in turn vends a Surface),
184 // and has
185 // plugins (i.e. end-users) use the Surface instead, letting us "hide" the
186 // consumer-side
187 // of the implementation.
188 //
189 // tl;dr: If ImageTexture is available, we use it, otherwise we use a
190 // SurfaceTexture.
191 // Coincidentally, if ImageTexture is available, we are also on an Android
192 // version that is
193 // running Vulkan, so we don't have to worry about it not being supported.
194 final SurfaceProducer entry;
195 if (!debugForceSurfaceProducerGlTextures && Build.VERSION.SDK_INT >= API_LEVELS.API_29) {
196 final long id = nextTextureId.getAndIncrement();
198 registerImageTexture(id, producer);
199 addOnTrimMemoryListener(producer);
200 Log.v(TAG, "New ImageReaderSurfaceProducer ID: " + id);
201 entry = producer;
202 } else {
203 // TODO(matanlurey): Actually have the class named "*Producer" to well, produce
204 // something. This is a code smell, but does guarantee the paths for both
205 // createSurfaceTexture and createSurfaceProducer doesn't diverge. As we get more
206 // confident in this API and any possible bugs (and have tests to check we don't
207 // regress), reconsider this pattern.
209 final SurfaceTextureSurfaceProducer producer =
210 new SurfaceTextureSurfaceProducer(texture.id(), handler, flutterJNI, texture);
211 Log.v(TAG, "New SurfaceTextureSurfaceProducer ID: " + texture.id());
212 entry = producer;
213 }
214 return entry;
215 }
216
217 /**
218 * Creates and returns a new {@link SurfaceTexture} managed by the Flutter engine that is also
219 * made available to Flutter code.
220 */
221 @NonNull
222 @Override
224 Log.v(TAG, "Creating a SurfaceTexture.");
225 final SurfaceTexture surfaceTexture = new SurfaceTexture(0);
226 return registerSurfaceTexture(surfaceTexture);
227 }
228
229 /**
230 * Registers and returns a {@link SurfaceTexture} managed by the Flutter engine that is also made
231 * available to Flutter code.
232 */
233 @NonNull
234 @Override
235 public SurfaceTextureEntry registerSurfaceTexture(@NonNull SurfaceTexture surfaceTexture) {
236 return registerSurfaceTexture(nextTextureId.getAndIncrement(), surfaceTexture);
237 }
238
239 /**
240 * Similar to {@link FlutterRenderer#registerSurfaceTexture} but with an existing @{code
241 * textureId}.
242 *
243 * @param surfaceTexture Surface texture to wrap.
244 * @param textureId A texture ID already created that should be assigned to the surface texture.
245 */
246 @NonNull
247 private SurfaceTextureEntry registerSurfaceTexture(
248 long textureId, @NonNull SurfaceTexture surfaceTexture) {
249 surfaceTexture.detachFromGLContext();
250 final SurfaceTextureRegistryEntry entry =
251 new SurfaceTextureRegistryEntry(textureId, surfaceTexture);
252 Log.v(TAG, "New SurfaceTexture ID: " + entry.id());
253 registerTexture(entry.id(), entry.textureWrapper());
255 return entry;
256 }
257
258 @NonNull
259 @Override
261 final ImageTextureRegistryEntry entry =
262 new ImageTextureRegistryEntry(nextTextureId.getAndIncrement());
263 Log.v(TAG, "New ImageTextureEntry ID: " + entry.id());
264 registerImageTexture(entry.id(), entry);
265 return entry;
266 }
267
268 @Override
269 public void onTrimMemory(int level) {
270 final Iterator<WeakReference<OnTrimMemoryListener>> iterator = onTrimMemoryListeners.iterator();
271 while (iterator.hasNext()) {
272 WeakReference<OnTrimMemoryListener> listenerRef = iterator.next();
273 final OnTrimMemoryListener listener = listenerRef.get();
274 if (listener != null) {
275 listener.onTrimMemory(level);
276 } else {
277 // Purge cleared refs to avoid accumulating a lot of dead listener
278 iterator.remove();
279 }
280 }
281 }
282
285 private final long id;
286 @NonNull private final SurfaceTextureWrapper textureWrapper;
287 private boolean released;
288 @Nullable private OnTrimMemoryListener trimMemoryListener;
289 @Nullable private OnFrameConsumedListener frameConsumedListener;
290
291 SurfaceTextureRegistryEntry(long id, @NonNull SurfaceTexture surfaceTexture) {
292 this.id = id;
293 Runnable onFrameConsumed =
294 () -> {
295 if (frameConsumedListener != null) {
296 frameConsumedListener.onFrameConsumed();
297 }
298 };
299 this.textureWrapper = new SurfaceTextureWrapper(surfaceTexture, onFrameConsumed);
300
301 // Even though we make sure to unregister the callback before releasing, as of
302 // Android O, SurfaceTexture has a data race when accessing the callback, so the
303 // callback may still be called by a stale reference after released==true and
304 // mNativeView==null.
305 SurfaceTexture.OnFrameAvailableListener onFrameListener =
306 texture -> {
307 if (released || !flutterJNI.isAttached()) {
308 // Even though we make sure to unregister the callback before releasing, as of
309 // Android O, SurfaceTexture has a data race when accessing the callback, so the
310 // callback may still be called by a stale reference after released==true and
311 // mNativeView==null.
312 return;
313 }
314 textureWrapper.markDirty();
315 scheduleEngineFrame();
316 };
317 // The callback relies on being executed on the UI thread (unsynchronised read of
318 // mNativeView and also the engine code check for platform thread in
319 // Shell::OnPlatformViewMarkTextureFrameAvailable), so we explicitly pass a Handler for the
320 // current thread.
321 this.surfaceTexture().setOnFrameAvailableListener(onFrameListener, new Handler());
322 }
323
324 @Override
325 public void onTrimMemory(int level) {
326 if (trimMemoryListener != null) {
327 trimMemoryListener.onTrimMemory(level);
328 }
329 }
330
331 private void removeListener() {
333 }
334
335 @NonNull
337 return textureWrapper;
338 }
339
340 @Override
341 @NonNull
342 public SurfaceTexture surfaceTexture() {
343 return textureWrapper.surfaceTexture();
344 }
345
346 @Override
347 public long id() {
348 return id;
349 }
350
351 @Override
352 public void release() {
353 if (released) {
354 return;
355 }
356 Log.v(TAG, "Releasing a SurfaceTexture (" + id + ").");
357 textureWrapper.release();
358 unregisterTexture(id);
359 removeListener();
360 released = true;
361 }
362
363 @Override
364 protected void finalize() throws Throwable {
365 try {
366 if (released) {
367 return;
368 }
369
370 handler.post(new TextureFinalizerRunnable(id, flutterJNI));
371 } finally {
372 super.finalize();
373 }
374 }
375
376 @Override
377 public void setOnFrameConsumedListener(@Nullable OnFrameConsumedListener listener) {
378 frameConsumedListener = listener;
379 }
380
381 @Override
382 public void setOnTrimMemoryListener(@Nullable OnTrimMemoryListener listener) {
383 trimMemoryListener = listener;
384 }
385 }
386
387 static final class TextureFinalizerRunnable implements Runnable {
388 private final long id;
389 private final FlutterJNI flutterJNI;
390
391 TextureFinalizerRunnable(long id, @NonNull FlutterJNI flutterJNI) {
392 this.id = id;
393 this.flutterJNI = flutterJNI;
394 }
395
396 @Override
397 public void run() {
398 if (!flutterJNI.isAttached()) {
399 return;
400 }
401 Log.v(TAG, "Releasing a Texture (" + id + ").");
402 flutterJNI.unregisterTexture(id);
403 }
404 }
405
406 // Keep a queue of ImageReaders.
407 // Each ImageReader holds acquired Images.
408 // When we acquire the next image, close any ImageReaders that don't have any
409 // more pending images.
410 @Keep
411 @TargetApi(API_LEVELS.API_29)
416 private static final String TAG = "ImageReaderSurfaceProducer";
417 private static final int MAX_IMAGES = 5;
418
419 // Flip when debugging to see verbose logs.
420 private static final boolean VERBOSE_LOGS = false;
421
422 // We must always cleanup on memory pressure on Android 14 due to a bug in Android.
423 // It is safe to do on all versions so we unconditionally have this set to true.
424 private static final boolean CLEANUP_ON_MEMORY_PRESSURE = true;
425
426 private final long id;
427
428 private boolean released;
429 // Will be true in tests and on Android API < 33.
430 private boolean ignoringFence = false;
431
432 private boolean trimOnMemoryPressure = CLEANUP_ON_MEMORY_PRESSURE;
433
434 // The requested width and height are updated by setSize.
435 private int requestedWidth = 1;
436 private int requestedHeight = 1;
437 // Whenever the requested width and height change we set this to be true so we
438 // create a new ImageReader (inside getSurface) with the correct width and height.
439 // We use this flag so that we lazily create the ImageReader only when a frame
440 // will be produced at that size.
441 private boolean createNewReader = true;
442
443 // State held to track latency of various stages.
444 private long lastDequeueTime = 0;
445 private long lastQueueTime = 0;
446 private long lastScheduleTime = 0;
447 private int numTrims = 0;
448
449 private Object lock = new Object();
450 // REQUIRED: The following fields must only be accessed when lock is held.
451 private final ArrayDeque<PerImageReader> imageReaderQueue = new ArrayDeque<PerImageReader>();
452 private final HashMap<ImageReader, PerImageReader> perImageReaders =
453 new HashMap<ImageReader, PerImageReader>();
454 private PerImage lastDequeuedImage = null;
455 private PerImageReader lastReaderDequeuedFrom = null;
456
457 /** Internal class: state held per Image produced by ImageReaders. */
458 private class PerImage {
459 public final Image image;
460 public final long queuedTime;
461
462 public PerImage(Image image, long queuedTime) {
463 this.image = image;
464 this.queuedTime = queuedTime;
465 }
466 }
467
468 /** Internal class: state held per ImageReader. */
469 private class PerImageReader {
470 public final ImageReader reader;
471 private final ArrayDeque<PerImage> imageQueue = new ArrayDeque<PerImage>();
472 private boolean closed = false;
473
474 private final ImageReader.OnImageAvailableListener onImageAvailableListener =
475 reader -> {
476 Image image = null;
477 try {
478 image = reader.acquireLatestImage();
479 } catch (IllegalStateException e) {
480 Log.e(TAG, "onImageAvailable acquireLatestImage failed: " + e);
481 }
482 if (image == null) {
483 return;
484 }
485 if (released || closed) {
486 image.close();
487 return;
488 }
489 onImage(reader, image);
490 };
491
492 public PerImageReader(ImageReader reader) {
493 this.reader = reader;
494 reader.setOnImageAvailableListener(
495 onImageAvailableListener, new Handler(Looper.getMainLooper()));
496 }
497
498 PerImage queueImage(Image image) {
499 if (closed) {
500 return null;
501 }
502 PerImage perImage = new PerImage(image, System.nanoTime());
503 imageQueue.add(perImage);
504 // If we fall too far behind we will skip some frames.
505 while (imageQueue.size() > 2) {
506 PerImage r = imageQueue.removeFirst();
507 if (VERBOSE_LOGS) {
508 Log.i(TAG, "" + reader.hashCode() + " force closed image=" + r.image.hashCode());
509 }
510 r.image.close();
511 }
512 return perImage;
513 }
514
515 PerImage dequeueImage() {
516 if (imageQueue.size() == 0) {
517 return null;
518 }
519 PerImage r = imageQueue.removeFirst();
520 return r;
521 }
522
523 /** returns true if we can prune this reader */
524 boolean canPrune() {
525 return imageQueue.size() == 0 && lastReaderDequeuedFrom != this;
526 }
527
528 void close() {
529 closed = true;
530 if (VERBOSE_LOGS) {
531 Log.i(TAG, "Closing reader=" + reader.hashCode());
532 }
533 reader.close();
534 imageQueue.clear();
535 }
536 }
537
538 double deltaMillis(long deltaNanos) {
539 double ms = (double) deltaNanos / (double) 1000000.0;
540 return ms;
541 }
542
543 PerImageReader getOrCreatePerImageReader(ImageReader reader) {
544 PerImageReader r = perImageReaders.get(reader);
545 if (r == null) {
546 r = new PerImageReader(reader);
547 perImageReaders.put(reader, r);
548 imageReaderQueue.add(r);
549 if (VERBOSE_LOGS) {
550 Log.i(TAG, "imageReaderQueue#=" + imageReaderQueue.size());
551 }
552 }
553 return r;
554 }
555
557 boolean change = false;
558 // Prune nodes from the head of the ImageReader queue.
559 while (imageReaderQueue.size() > 1) {
560 PerImageReader r = imageReaderQueue.peekFirst();
561 if (!r.canPrune()) {
562 // No more ImageReaders can be pruned this round.
563 break;
564 }
565 imageReaderQueue.removeFirst();
566 perImageReaders.remove(r.reader);
567 r.close();
568 change = true;
569 }
570 if (change && VERBOSE_LOGS) {
571 Log.i(TAG, "Pruned image reader queue length=" + imageReaderQueue.size());
572 }
573 }
574
575 void onImage(ImageReader reader, Image image) {
576 PerImage queuedImage = null;
577 synchronized (lock) {
578 PerImageReader perReader = getOrCreatePerImageReader(reader);
579 queuedImage = perReader.queueImage(image);
580 }
581 if (queuedImage == null) {
582 // We got a late image.
583 return;
584 }
585 if (VERBOSE_LOGS) {
586 if (lastQueueTime != 0) {
587 long now = System.nanoTime();
588 long queueDelta = now - lastQueueTime;
589 Log.i(
590 TAG,
591 ""
592 + reader.hashCode()
593 + " enqueued image="
594 + queuedImage.image.hashCode()
595 + " queueDelta="
596 + deltaMillis(queueDelta));
597 lastQueueTime = now;
598 } else {
599 lastQueueTime = System.nanoTime();
600 }
601 }
602 scheduleEngineFrame();
603 }
604
605 PerImage dequeueImage() {
606 PerImage r = null;
607 synchronized (lock) {
608 for (PerImageReader reader : imageReaderQueue) {
609 r = reader.dequeueImage();
610 if (r == null) {
611 // This reader is probably about to get pruned.
612 continue;
613 }
614 if (VERBOSE_LOGS) {
615 if (lastDequeueTime != 0) {
616 long now = System.nanoTime();
617 long dequeueDelta = now - lastDequeueTime;
618 long queuedFor = now - r.queuedTime;
619 long scheduleDelay = now - lastScheduleTime;
620 Log.i(
621 TAG,
622 ""
623 + reader.reader.hashCode()
624 + " dequeued image="
625 + r.image.hashCode()
626 + " queuedFor= "
627 + deltaMillis(queuedFor)
628 + " dequeueDelta="
629 + deltaMillis(dequeueDelta)
630 + " scheduleDelay="
631 + deltaMillis(scheduleDelay));
632 lastDequeueTime = now;
633 } else {
634 lastDequeueTime = System.nanoTime();
635 }
636 }
637 if (lastDequeuedImage != null) {
638 if (VERBOSE_LOGS) {
639 Log.i(
640 TAG,
641 ""
642 + lastReaderDequeuedFrom.reader.hashCode()
643 + " closing image="
644 + lastDequeuedImage.image.hashCode());
645 }
646 // We must keep the last image dequeued open until we are done presenting
647 // it. We have just dequeued a new image (r). Close the previously dequeued
648 // image.
649 lastDequeuedImage.image.close();
650 lastDequeuedImage = null;
651 }
652 // Remember the last image and reader dequeued from. We do this because we must
653 // keep both of these alive until we are done presenting the image.
654 lastDequeuedImage = r;
655 lastReaderDequeuedFrom = reader;
656 break;
657 }
658 pruneImageReaderQueue();
659 }
660 return r;
661 }
662
663 @Override
664 public void onTrimMemory(int level) {
665 if (!trimOnMemoryPressure) {
666 return;
667 }
668 if (level < ComponentCallbacks2.TRIM_MEMORY_BACKGROUND) {
669 return;
670 }
671 synchronized (lock) {
672 numTrims++;
673 }
674 cleanup();
675 createNewReader = true;
676 }
677
678 private void releaseInternal() {
679 cleanup();
680 released = true;
681 }
682
683 private void cleanup() {
684 synchronized (lock) {
685 for (PerImageReader pir : perImageReaders.values()) {
686 if (lastReaderDequeuedFrom == pir) {
687 lastReaderDequeuedFrom = null;
688 }
689 pir.close();
690 }
691 perImageReaders.clear();
692 if (lastDequeuedImage != null) {
693 lastDequeuedImage.image.close();
694 lastDequeuedImage = null;
695 }
696 if (lastReaderDequeuedFrom != null) {
697 lastReaderDequeuedFrom.close();
698 lastReaderDequeuedFrom = null;
699 }
700 imageReaderQueue.clear();
701 }
702 }
703
704 @TargetApi(API_LEVELS.API_33)
705 private void waitOnFence(Image image) {
706 try {
707 SyncFence fence = image.getFence();
708 fence.awaitForever();
709 } catch (IOException e) {
710 // Drop.
711 }
712 }
713
714 private void maybeWaitOnFence(Image image) {
715 if (image == null) {
716 return;
717 }
718 if (ignoringFence) {
719 return;
720 }
721 if (Build.VERSION.SDK_INT >= API_LEVELS.API_33) {
722 // The fence API is only available on Android >= 33.
723 waitOnFence(image);
724 return;
725 }
726 // Log once per ImageTextureEntry.
727 ignoringFence = true;
728 Log.w(TAG, "ImageTextureEntry can't wait on the fence on Android < 33");
729 }
730
732 this.id = id;
733 }
734
735 @Override
736 public long id() {
737 return id;
738 }
739
740 @Override
741 public void release() {
742 if (released) {
743 return;
744 }
745 releaseInternal();
746 unregisterTexture(id);
747 }
748
749 @Override
750 public void setSize(int width, int height) {
751 // Clamp to a minimum of 1. A 0x0 texture is a runtime exception in ImageReader.
752 width = Math.max(1, width);
753 height = Math.max(1, height);
754
755 if (requestedWidth == width && requestedHeight == height) {
756 // No size change.
757 return;
758 }
759 this.createNewReader = true;
760 this.requestedHeight = height;
761 this.requestedWidth = width;
762 }
763
764 @Override
765 public int getWidth() {
766 return this.requestedWidth;
767 }
768
769 @Override
770 public int getHeight() {
771 return this.requestedHeight;
772 }
773
774 @Override
775 public Surface getSurface() {
776 PerImageReader pir = getActiveReader();
777 if (VERBOSE_LOGS) {
778 Log.i(TAG, "" + pir.reader.hashCode() + " returning surface to render a new frame.");
779 }
780 return pir.reader.getSurface();
781 }
782
783 @Override
784 public void scheduleFrame() {
785 if (VERBOSE_LOGS) {
786 long now = System.nanoTime();
787 if (lastScheduleTime != 0) {
788 long delta = now - lastScheduleTime;
789 Log.v(TAG, "scheduleFrame delta=" + deltaMillis(delta));
790 }
791 lastScheduleTime = now;
792 }
793 scheduleEngineFrame();
794 }
795
796 @Override
797 @TargetApi(API_LEVELS.API_29)
798 public Image acquireLatestImage() {
799 PerImage r = dequeueImage();
800 if (r == null) {
801 return null;
802 }
803 maybeWaitOnFence(r.image);
804 return r.image;
805 }
806
807 private PerImageReader getActiveReader() {
808 synchronized (lock) {
809 if (createNewReader) {
810 createNewReader = false;
811 // Create a new ImageReader and add it to the queue.
812 ImageReader reader = createImageReader();
813 if (VERBOSE_LOGS) {
814 Log.i(
815 TAG,
816 "" + reader.hashCode() + " created w=" + requestedWidth + " h=" + requestedHeight);
817 }
818 return getOrCreatePerImageReader(reader);
819 }
820 return imageReaderQueue.peekLast();
821 }
822 }
823
824 @Override
825 protected void finalize() throws Throwable {
826 try {
827 if (released) {
828 return;
829 }
830 releaseInternal();
831 handler.post(new TextureFinalizerRunnable(id, flutterJNI));
832 } finally {
833 super.finalize();
834 }
835 }
836
837 @TargetApi(API_LEVELS.API_33)
838 private ImageReader createImageReader33() {
839 final ImageReader.Builder builder = new ImageReader.Builder(requestedWidth, requestedHeight);
840 // Allow for double buffering.
841 builder.setMaxImages(MAX_IMAGES);
842 // Use PRIVATE image format so that we can support video decoding.
843 // TODO(johnmccutchan): Should we always use PRIVATE here? It may impact our ability to
844 // read back texture data. If we don't always want to use it, how do we decide when to
845 // use it or not? Perhaps PlatformViews can indicate if they may contain DRM'd content.
846 // I need to investigate how PRIVATE impacts our ability to take screenshots or capture
847 // the output of Flutter application.
848 builder.setImageFormat(ImageFormat.PRIVATE);
849 // Hint that consumed images will only be read by GPU.
850 builder.setUsage(HardwareBuffer.USAGE_GPU_SAMPLED_IMAGE);
851 final ImageReader reader = builder.build();
852 return reader;
853 }
854
855 @TargetApi(API_LEVELS.API_29)
856 private ImageReader createImageReader29() {
857 final ImageReader reader =
858 ImageReader.newInstance(
859 requestedWidth,
860 requestedHeight,
861 ImageFormat.PRIVATE,
862 MAX_IMAGES,
863 HardwareBuffer.USAGE_GPU_SAMPLED_IMAGE);
864 return reader;
865 }
866
867 private ImageReader createImageReader() {
868 if (Build.VERSION.SDK_INT >= API_LEVELS.API_33) {
869 return createImageReader33();
870 } else if (Build.VERSION.SDK_INT >= API_LEVELS.API_29) {
871 return createImageReader29();
872 }
873 throw new UnsupportedOperationException(
874 "ImageReaderPlatformViewRenderTarget requires API version 29+");
875 }
876
877 @VisibleForTesting
878 public void disableFenceForTest() {
879 // Roboelectric's implementation of SyncFence is borked.
880 ignoringFence = true;
881 }
882
883 @VisibleForTesting
884 public int numImageReaders() {
885 synchronized (lock) {
886 return imageReaderQueue.size();
887 }
888 }
889
890 @VisibleForTesting
891 public int numTrims() {
892 synchronized (lock) {
893 return numTrims;
894 }
895 }
896
897 @VisibleForTesting
898 public int numImages() {
899 int r = 0;
900 synchronized (lock) {
901 for (PerImageReader reader : imageReaderQueue) {
902 r += reader.imageQueue.size();
903 }
904 }
905 return r;
906 }
907 }
908
909 @Keep
912 private static final String TAG = "ImageTextureRegistryEntry";
913 private final long id;
914 private boolean released;
915 private boolean ignoringFence = false;
916 private Image image;
917
919 this.id = id;
920 }
921
922 @Override
923 public long id() {
924 return id;
925 }
926
927 @Override
928 public void release() {
929 if (released) {
930 return;
931 }
932 released = true;
933 if (image != null) {
934 image.close();
935 image = null;
936 }
937 unregisterTexture(id);
938 }
939
940 @Override
941 public void pushImage(Image image) {
942 if (released) {
943 return;
944 }
945 Image toClose;
946 synchronized (this) {
947 toClose = this.image;
948 this.image = image;
949 }
950 // Close the previously pushed buffer.
951 if (toClose != null) {
952 Log.e(TAG, "Dropping PlatformView Frame");
953 toClose.close();
954 }
955 if (image != null) {
956 scheduleEngineFrame();
957 }
958 }
959
960 @TargetApi(API_LEVELS.API_33)
961 private void waitOnFence(Image image) {
962 try {
963 SyncFence fence = image.getFence();
964 fence.awaitForever();
965 } catch (IOException e) {
966 // Drop.
967 }
968 }
969
970 @TargetApi(API_LEVELS.API_29)
971 private void maybeWaitOnFence(Image image) {
972 if (image == null) {
973 return;
974 }
975 if (ignoringFence) {
976 return;
977 }
978 if (Build.VERSION.SDK_INT >= API_LEVELS.API_33) {
979 // The fence API is only available on Android >= 33.
980 waitOnFence(image);
981 return;
982 }
983 // Log once per ImageTextureEntry.
984 ignoringFence = true;
985 Log.w(TAG, "ImageTextureEntry can't wait on the fence on Android < 33");
986 }
987
988 @Override
989 @TargetApi(API_LEVELS.API_29)
991 Image r;
992 synchronized (this) {
993 r = this.image;
994 this.image = null;
995 }
996 maybeWaitOnFence(r);
997 return r;
998 }
999
1000 @Override
1001 protected void finalize() throws Throwable {
1002 try {
1003 if (released) {
1004 return;
1005 }
1006 if (image != null) {
1007 // Be sure to finalize any cached image.
1008 image.close();
1009 image = null;
1010 }
1011 released = true;
1012 handler.post(new TextureFinalizerRunnable(id, flutterJNI));
1013 } finally {
1014 super.finalize();
1015 }
1016 }
1017 }
1018 // ------ END TextureRegistry IMPLEMENTATION ----
1019
1020 /**
1021 * Notifies Flutter that the given {@code surface} was created and is available for Flutter
1022 * rendering.
1023 *
1024 * <p>If called more than once, the current native resources are released. This can be undesired
1025 * if the Engine expects to reuse this surface later. For example, this is true when platform
1026 * views are displayed in a frame, and then removed in the next frame.
1027 *
1028 * <p>To avoid releasing the current surface resources, set {@code keepCurrentSurface} to true.
1029 *
1030 * <p>See {@link android.view.SurfaceHolder.Callback} and {@link
1031 * android.view.TextureView.SurfaceTextureListener}
1032 *
1033 * @param surface The render surface.
1034 * @param onlySwap True if the current active surface should not be detached.
1035 */
1036 public void startRenderingToSurface(@NonNull Surface surface, boolean onlySwap) {
1037 if (!onlySwap) {
1038 // Stop rendering to the surface releases the associated native resources, which causes
1039 // a glitch when toggling between rendering to an image view (hybrid composition) and
1040 // rendering directly to a Surface or Texture view. For more,
1041 // https://github.com/flutter/flutter/issues/95343
1043 }
1044
1045 this.surface = surface;
1046
1047 if (onlySwap) {
1048 // In the swap case we are just swapping the surface that we render to.
1049 flutterJNI.onSurfaceWindowChanged(surface);
1050 } else {
1051 // In the non-swap case we are creating a new surface to render to.
1052 flutterJNI.onSurfaceCreated(surface);
1053 }
1054 }
1055
1056 /**
1057 * Swaps the {@link Surface} used to render the current frame.
1058 *
1059 * <p>In hybrid composition, the root surfaces changes from {@link
1060 * android.view.SurfaceHolder#getSurface()} to {@link android.media.ImageReader#getSurface()} when
1061 * a platform view is in the current frame.
1062 */
1063 public void swapSurface(@NonNull Surface surface) {
1064 this.surface = surface;
1065 flutterJNI.onSurfaceWindowChanged(surface);
1066 }
1067
1068 /**
1069 * Notifies Flutter that a {@code surface} previously registered with {@link
1070 * #startRenderingToSurface(Surface, boolean)} has changed size to the given {@code width} and
1071 * {@code height}.
1072 *
1073 * <p>See {@link android.view.SurfaceHolder.Callback} and {@link
1074 * android.view.TextureView.SurfaceTextureListener}
1075 */
1076 public void surfaceChanged(int width, int height) {
1077 flutterJNI.onSurfaceChanged(width, height);
1078 }
1079
1080 /**
1081 * Notifies Flutter that a {@code surface} previously registered with {@link
1082 * #startRenderingToSurface(Surface, boolean)} has been destroyed and needs to be released and
1083 * cleaned up on the Flutter side.
1084 *
1085 * <p>See {@link android.view.SurfaceHolder.Callback} and {@link
1086 * android.view.TextureView.SurfaceTextureListener}
1087 */
1089 if (surface != null) {
1090 flutterJNI.onSurfaceDestroyed();
1091
1092 // TODO(mattcarroll): the source of truth for this call should be FlutterJNI, which is
1093 // where the call to onFlutterUiDisplayed() comes from. However, no such native callback
1094 // exists yet, so until the engine and FlutterJNI are configured to call us back when
1095 // rendering stops, we will manually monitor that change here.
1096 if (isDisplayingFlutterUi) {
1097 flutterUiDisplayListener.onFlutterUiNoLongerDisplayed();
1098 }
1099
1100 isDisplayingFlutterUi = false;
1101 surface = null;
1102 }
1103 }
1104
1105 /**
1106 * Notifies Flutter that the viewport metrics, e.g. window height and width, have changed.
1107 *
1108 * <p>If the width, height, or devicePixelRatio are less than or equal to 0, this update is
1109 * ignored.
1110 *
1111 * @param viewportMetrics The metrics to send to the Dart application.
1112 */
1113 public void setViewportMetrics(@NonNull ViewportMetrics viewportMetrics) {
1114 // We might get called with just the DPR if width/height aren't available yet.
1115 // Just ignore, as it will get called again when width/height are set.
1116 if (!viewportMetrics.validate()) {
1117 return;
1118 }
1119 Log.v(
1120 TAG,
1121 "Setting viewport metrics\n"
1122 + "Size: "
1123 + viewportMetrics.width
1124 + " x "
1125 + viewportMetrics.height
1126 + "\n"
1127 + "Padding - L: "
1128 + viewportMetrics.viewPaddingLeft
1129 + ", T: "
1130 + viewportMetrics.viewPaddingTop
1131 + ", R: "
1132 + viewportMetrics.viewPaddingRight
1133 + ", B: "
1134 + viewportMetrics.viewPaddingBottom
1135 + "\n"
1136 + "Insets - L: "
1137 + viewportMetrics.viewInsetLeft
1138 + ", T: "
1139 + viewportMetrics.viewInsetTop
1140 + ", R: "
1141 + viewportMetrics.viewInsetRight
1142 + ", B: "
1143 + viewportMetrics.viewInsetBottom
1144 + "\n"
1145 + "System Gesture Insets - L: "
1146 + viewportMetrics.systemGestureInsetLeft
1147 + ", T: "
1148 + viewportMetrics.systemGestureInsetTop
1149 + ", R: "
1150 + viewportMetrics.systemGestureInsetRight
1151 + ", B: "
1152 + viewportMetrics.systemGestureInsetRight
1153 + "\n"
1154 + "Display Features: "
1155 + viewportMetrics.displayFeatures.size());
1156
1157 int[] displayFeaturesBounds = new int[viewportMetrics.displayFeatures.size() * 4];
1158 int[] displayFeaturesType = new int[viewportMetrics.displayFeatures.size()];
1159 int[] displayFeaturesState = new int[viewportMetrics.displayFeatures.size()];
1160 for (int i = 0; i < viewportMetrics.displayFeatures.size(); i++) {
1161 DisplayFeature displayFeature = viewportMetrics.displayFeatures.get(i);
1162 displayFeaturesBounds[4 * i] = displayFeature.bounds.left;
1163 displayFeaturesBounds[4 * i + 1] = displayFeature.bounds.top;
1164 displayFeaturesBounds[4 * i + 2] = displayFeature.bounds.right;
1165 displayFeaturesBounds[4 * i + 3] = displayFeature.bounds.bottom;
1166 displayFeaturesType[i] = displayFeature.type.encodedValue;
1167 displayFeaturesState[i] = displayFeature.state.encodedValue;
1168 }
1169
1170 flutterJNI.setViewportMetrics(
1171 viewportMetrics.devicePixelRatio,
1172 viewportMetrics.width,
1173 viewportMetrics.height,
1174 viewportMetrics.viewPaddingTop,
1175 viewportMetrics.viewPaddingRight,
1176 viewportMetrics.viewPaddingBottom,
1177 viewportMetrics.viewPaddingLeft,
1178 viewportMetrics.viewInsetTop,
1179 viewportMetrics.viewInsetRight,
1180 viewportMetrics.viewInsetBottom,
1181 viewportMetrics.viewInsetLeft,
1182 viewportMetrics.systemGestureInsetTop,
1183 viewportMetrics.systemGestureInsetRight,
1184 viewportMetrics.systemGestureInsetBottom,
1185 viewportMetrics.systemGestureInsetLeft,
1186 viewportMetrics.physicalTouchSlop,
1187 displayFeaturesBounds,
1188 displayFeaturesType,
1189 displayFeaturesState);
1190 }
1191
1192 // TODO(mattcarroll): describe the native behavior that this invokes
1193 // TODO(mattcarroll): determine if this is nullable or nonnull
1194 public Bitmap getBitmap() {
1195 return flutterJNI.getBitmap();
1196 }
1197
1198 // TODO(mattcarroll): describe the native behavior that this invokes
1199 public void dispatchPointerDataPacket(@NonNull ByteBuffer buffer, int position) {
1200 flutterJNI.dispatchPointerDataPacket(buffer, position);
1201 }
1202
1203 // TODO(mattcarroll): describe the native behavior that this invokes
1204 private void registerTexture(long textureId, @NonNull SurfaceTextureWrapper textureWrapper) {
1205 flutterJNI.registerTexture(textureId, textureWrapper);
1206 }
1207
1208 private void registerImageTexture(
1209 long textureId, @NonNull TextureRegistry.ImageConsumer imageTexture) {
1210 flutterJNI.registerImageTexture(textureId, imageTexture);
1211 }
1212
1213 private void scheduleEngineFrame() {
1214 flutterJNI.scheduleFrame();
1215 }
1216
1217 // TODO(mattcarroll): describe the native behavior that this invokes
1218 private void markTextureFrameAvailable(long textureId) {
1219 flutterJNI.markTextureFrameAvailable(textureId);
1220 }
1221
1222 // TODO(mattcarroll): describe the native behavior that this invokes
1223 private void unregisterTexture(long textureId) {
1224 flutterJNI.unregisterTexture(textureId);
1225 }
1226
1227 // TODO(mattcarroll): describe the native behavior that this invokes
1229 return flutterJNI.getIsSoftwareRenderingEnabled();
1230 }
1231
1232 // TODO(mattcarroll): describe the native behavior that this invokes
1234 flutterJNI.setAccessibilityFeatures(flags);
1235 }
1236
1237 // TODO(mattcarroll): describe the native behavior that this invokes
1238 public void setSemanticsEnabled(boolean enabled) {
1239 flutterJNI.setSemanticsEnabled(enabled);
1240 }
1241
1242 // TODO(mattcarroll): describe the native behavior that this invokes
1244 int nodeId, int action, @Nullable ByteBuffer args, int argsPosition) {
1245 flutterJNI.dispatchSemanticsAction(nodeId, action, args, argsPosition);
1246 }
1247
1248 /**
1249 * Mutable data structure that holds all viewport metrics properties that Flutter cares about.
1250 *
1251 * <p>All distance measurements, e.g., width, height, padding, viewInsets, are measured in device
1252 * pixels, not logical pixels.
1253 */
1254 public static final class ViewportMetrics {
1255 /** A value that indicates the setting has not been set. */
1256 public static final int unsetValue = -1;
1257
1258 public float devicePixelRatio = 1.0f;
1259 public int width = 0;
1260 public int height = 0;
1261 public int viewPaddingTop = 0;
1262 public int viewPaddingRight = 0;
1263 public int viewPaddingBottom = 0;
1264 public int viewPaddingLeft = 0;
1265 public int viewInsetTop = 0;
1266 public int viewInsetRight = 0;
1267 public int viewInsetBottom = 0;
1268 public int viewInsetLeft = 0;
1274
1275 /**
1276 * Whether this instance contains valid metrics for the Flutter application.
1277 *
1278 * @return True if width, height, and devicePixelRatio are > 0; false otherwise.
1279 */
1280 boolean validate() {
1281 return width > 0 && height > 0 && devicePixelRatio > 0;
1282 }
1283
1284 public List<DisplayFeature> displayFeatures = new ArrayList<>();
1285 }
1286
1287 /**
1288 * Description of a physical feature on the display.
1289 *
1290 * <p>A display feature is a distinctive physical attribute located within the display panel of
1291 * the device. It can intrude into the application window space and create a visual distortion,
1292 * visual or touch discontinuity, make some area invisible or create a logical divider or
1293 * separation in the screen space.
1294 *
1295 * <p>Based on {@link androidx.window.layout.DisplayFeature}, with added support for cutouts.
1296 */
1297 public static final class DisplayFeature {
1298 public final Rect bounds;
1301
1303 this.bounds = bounds;
1304 this.type = type;
1305 this.state = state;
1306 }
1307
1309 this.bounds = bounds;
1310 this.type = type;
1311 this.state = DisplayFeatureState.UNKNOWN;
1312 }
1313 }
1314
1315 /**
1316 * Types of display features that can appear on the viewport.
1317 *
1318 * <p>Some, like {@link #FOLD}, can be reported without actually occluding the screen. They are
1319 * useful for knowing where the display is bent or has a crease. The {@link DisplayFeature#bounds}
1320 * can be 0-width in such cases.
1321 */
1323 /**
1324 * Type of display feature not yet known to Flutter. This can happen if WindowManager is updated
1325 * with new types. The {@link DisplayFeature#bounds} is the only known property.
1326 */
1328
1329 /**
1330 * A fold in the flexible display that does not occlude the screen. Corresponds to {@link
1331 * androidx.window.layout.FoldingFeature.OcclusionType#NONE}
1332 */
1334
1335 /**
1336 * Splits the display in two separate panels that can fold. Occludes the screen. Corresponds to
1337 * {@link androidx.window.layout.FoldingFeature.OcclusionType#FULL}
1338 */
1340
1341 /**
1342 * Area of the screen that usually houses cameras or sensors. Occludes the screen. Corresponds
1343 * to {@link android.view.DisplayCutout}
1344 */
1346
1347 public final int encodedValue;
1348
1349 DisplayFeatureType(int encodedValue) {
1350 this.encodedValue = encodedValue;
1351 }
1352 }
1353
1354 /**
1355 * State of the display feature.
1356 *
1357 * <p>For foldables, the state is the posture. For cutouts, this property is {@link #UNKNOWN}
1358 */
1360 /** The display feature is a cutout or this state is new and not yet known to Flutter. */
1362
1363 /**
1364 * The foldable device is completely open. The screen space that is presented to the user is
1365 * flat. Corresponds to {@link androidx.window.layout.FoldingFeature.State#FLAT}
1366 */
1368
1369 /**
1370 * The foldable device's hinge is in an intermediate position between opened and closed state.
1371 * There is a non-flat angle between parts of the flexible screen or between physical display
1372 * panels. Corresponds to {@link androidx.window.layout.FoldingFeature.State#HALF_OPENED}
1373 */
1375
1376 public final int encodedValue;
1377
1378 DisplayFeatureState(int encodedValue) {
1379 this.encodedValue = encodedValue;
1380 }
1381 }
1382}
static final int API_29
Definition: Build.java:19
static final int API_33
Definition: Build.java:23
static void v(@NonNull String tag, @NonNull String message)
Definition: Log.java:40
static void e(@NonNull String tag, @NonNull String message)
Definition: Log.java:84
static void i(@NonNull String tag, @NonNull String message)
Definition: Log.java:52
void unregisterTexture(long textureId)
DisplayFeature(Rect bounds, DisplayFeatureType type, DisplayFeatureState state)
SurfaceTextureRegistryEntry(long id, @NonNull SurfaceTexture surfaceTexture)
SurfaceTextureEntry registerSurfaceTexture(@NonNull SurfaceTexture surfaceTexture)
void removeIsDisplayingFlutterUiListener(@NonNull FlutterUiDisplayListener listener)
void startRenderingToSurface(@NonNull Surface surface, boolean onlySwap)
void dispatchSemanticsAction(int nodeId, int action, @Nullable ByteBuffer args, int argsPosition)
void removeOnTrimMemoryListener(@NonNull OnTrimMemoryListener listener)
void addOnTrimMemoryListener(@NonNull OnTrimMemoryListener listener)
void addIsDisplayingFlutterUiListener(@NonNull FlutterUiDisplayListener listener)
void setViewportMetrics(@NonNull ViewportMetrics viewportMetrics)
void dispatchPointerDataPacket(@NonNull ByteBuffer buffer, int position)
VkSurfaceKHR surface
Definition: main.cc:49
FlutterSemanticsFlag flags
G_BEGIN_DECLS G_MODULE_EXPORT FlValue * args
FlTexture * texture
sk_sp< const SkImage > image
Definition: SkRecords.h:269
void Log(const char *format,...) SK_PRINTF_LIKE(1
Definition: TestRunner.cpp:137
def Build(configs, env, options)
Definition: build.py:232
DEF_SWITCHES_START aot vmservice shared library Name of the *so containing AOT compiled Dart assets for launching the service isolate vm snapshot The VM snapshot data that will be memory mapped as read only SnapshotAssetPath must be present isolate snapshot The isolate snapshot data that will be memory mapped as read only SnapshotAssetPath must be present cache dir Path to the cache directory This is different from the persistent_cache_path in embedder which is used for Skia shader cache icu native lib Path to the library file that exports the ICU data vm service The hostname IP address on which the Dart VM Service should be served If not defaults to or::depending on whether ipv6 is specified vm service A custom Dart VM Service port The default is to pick a randomly available open port disable vm Disable the Dart VM Service The Dart VM Service is never available in release mode disable vm service Disable mDNS Dart VM Service publication Bind to the IPv6 localhost address for the Dart VM Service Ignored if vm service host is set endless trace buffer
Definition: switches.h:126
CanvasImage Image
Definition: dart_ui.cc:55
TRect< Scalar > Rect
Definition: rect.h:769
#define TAG()
int32_t height
int32_t width
const uintptr_t id