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