Flutter Engine
The Flutter Engine
Loading...
Searching...
No Matches
FlutterRendererTest.java
Go to the documentation of this file.
1package io.flutter.embedding.engine.renderer;
2
3import static android.content.ComponentCallbacks2.TRIM_MEMORY_COMPLETE;
4import static org.junit.Assert.assertArrayEquals;
5import static org.junit.Assert.assertEquals;
6import static org.junit.Assert.assertNotNull;
7import static org.junit.Assert.assertNull;
8import static org.mockito.ArgumentMatchers.any;
9import static org.mockito.ArgumentMatchers.anyFloat;
10import static org.mockito.ArgumentMatchers.anyInt;
11import static org.mockito.ArgumentMatchers.eq;
12import static org.mockito.Mockito.mock;
13import static org.mockito.Mockito.never;
14import static org.mockito.Mockito.spy;
15import static org.mockito.Mockito.times;
16import static org.mockito.Mockito.verify;
17import static org.mockito.Mockito.when;
18import static org.robolectric.Shadows.shadowOf;
19
20import android.graphics.Canvas;
21import android.graphics.Rect;
22import android.graphics.SurfaceTexture;
23import android.media.Image;
24import android.os.Looper;
25import android.view.Surface;
26import androidx.test.ext.junit.runners.AndroidJUnit4;
27import io.flutter.embedding.engine.FlutterJNI;
28import io.flutter.view.TextureRegistry;
29import java.util.concurrent.CountDownLatch;
30import java.util.concurrent.atomic.AtomicInteger;
31import org.junit.Before;
32import org.junit.Test;
33import org.junit.runner.RunWith;
34import org.mockito.ArgumentCaptor;
35import org.robolectric.annotation.Config;
36
37@Config(manifest = Config.NONE)
38@RunWith(AndroidJUnit4.class)
39public class FlutterRendererTest {
40
41 private FlutterJNI fakeFlutterJNI;
42 private Surface fakeSurface;
43 private Surface fakeSurface2;
44
45 @Before
46 public void init() {
47 // Uncomment the following line to enable logging output in test.
48 // ShadowLog.stream = System.out;
49 }
50
51 @Before
52 public void setup() {
53 fakeFlutterJNI = mock(FlutterJNI.class);
54 fakeSurface = mock(Surface.class);
55 fakeSurface2 = mock(Surface.class);
56 }
57
58 @Test
60 // Setup the test.
61 Surface fakeSurface = mock(Surface.class);
62 FlutterRenderer flutterRenderer = new FlutterRenderer(fakeFlutterJNI);
63
64 // Execute the behavior under test.
65 flutterRenderer.startRenderingToSurface(fakeSurface, false);
66
67 // Verify the behavior under test.
68 verify(fakeFlutterJNI, times(1)).onSurfaceCreated(eq(fakeSurface));
69 }
70
71 @Test
73 // Setup the test.
74 Surface fakeSurface = mock(Surface.class);
75 FlutterRenderer flutterRenderer = new FlutterRenderer(fakeFlutterJNI);
76
77 flutterRenderer.startRenderingToSurface(fakeSurface, false);
78
79 // Execute the behavior under test.
80 flutterRenderer.surfaceChanged(100, 50);
81
82 // Verify the behavior under test.
83 verify(fakeFlutterJNI, times(1)).onSurfaceChanged(eq(100), eq(50));
84 }
85
86 @Test
88 // Setup the test.
89 Surface fakeSurface = mock(Surface.class);
90 FlutterRenderer flutterRenderer = new FlutterRenderer(fakeFlutterJNI);
91
92 flutterRenderer.startRenderingToSurface(fakeSurface, false);
93
94 // Execute the behavior under test.
95 flutterRenderer.stopRenderingToSurface();
96
97 // Verify the behavior under test.
98 verify(fakeFlutterJNI, times(1)).onSurfaceDestroyed();
99 }
100
101 @Test
103 // Setup the test.
104 Surface fakeSurface2 = mock(Surface.class);
105 FlutterRenderer flutterRenderer = new FlutterRenderer(fakeFlutterJNI);
106
107 flutterRenderer.startRenderingToSurface(fakeSurface, false);
108
109 // Execute behavior under test.
110 flutterRenderer.startRenderingToSurface(fakeSurface2, false);
111
112 // Verify behavior under test.
113 verify(fakeFlutterJNI, times(1)).onSurfaceDestroyed(); // notification of 1st surface's removal.
114 }
115
116 @Test
118 // Setup the test.
119 FlutterRenderer flutterRenderer = new FlutterRenderer(fakeFlutterJNI);
120
121 flutterRenderer.startRenderingToSurface(fakeSurface, false);
122
123 // Execute the behavior under test.
124 flutterRenderer.stopRenderingToSurface();
125
126 // Verify behavior under test.
127 verify(fakeFlutterJNI, times(1)).onSurfaceDestroyed();
128 }
129
130 @Test
132 // Setup the test.
133 FlutterRenderer flutterRenderer = new FlutterRenderer(fakeFlutterJNI);
134
135 flutterRenderer.startRenderingToSurface(fakeSurface, false);
136
137 flutterRenderer.startRenderingToSurface(fakeSurface, false);
138
139 // Verify behavior under test.
140 verify(fakeFlutterJNI, times(1)).onSurfaceDestroyed();
141 }
142
143 @Test
145 // Setup the test.
146 FlutterRenderer flutterRenderer = new FlutterRenderer(fakeFlutterJNI);
147
148 flutterRenderer.startRenderingToSurface(fakeSurface, false);
149
150 flutterRenderer.startRenderingToSurface(fakeSurface, true);
151
152 // Verify behavior under test.
153 verify(fakeFlutterJNI, never()).onSurfaceDestroyed();
154 }
155
156 @Test
158 // Setup the test.
159 FlutterRenderer flutterRenderer = new FlutterRenderer(fakeFlutterJNI);
160
162
163 FlutterRenderer.SurfaceTextureRegistryEntry entry =
165
166 flutterRenderer.startRenderingToSurface(fakeSurface, false);
167
168 // Execute the behavior under test.
169 flutterRenderer.stopRenderingToSurface();
170
171 // Verify behavior under test.
172 verify(fakeFlutterJNI, times(0)).markTextureFrameAvailable(eq(entry.id()));
173 }
174
175 @Test
177 // Setup the test.
178 FlutterRenderer flutterRenderer = new FlutterRenderer(fakeFlutterJNI);
179
181
182 SurfaceTexture surfaceTexture = new SurfaceTexture(0);
183
184 // Execute the behavior under test.
185 FlutterRenderer.SurfaceTextureRegistryEntry entry =
187 flutterRenderer.registerSurfaceTexture(surfaceTexture);
188
189 flutterRenderer.startRenderingToSurface(fakeSurface, false);
190
191 // Verify behavior under test.
192 assertEquals(surfaceTexture, entry.surfaceTexture());
193
194 verify(fakeFlutterJNI, times(1)).registerTexture(eq(entry.id()), eq(entry.textureWrapper()));
195 }
196
197 @Test
199 // Setup the test.
200 FlutterJNI fakeFlutterJNI = mock(FlutterJNI.class);
201 when(fakeFlutterJNI.isAttached()).thenReturn(true);
202 FlutterRenderer flutterRenderer = new FlutterRenderer(fakeFlutterJNI);
203
205
206 FlutterRenderer.SurfaceTextureRegistryEntry entry =
208 long id = entry.id();
209
210 flutterRenderer.startRenderingToSurface(fakeSurface, false);
211
212 // Execute the behavior under test.
213 runFinalization(entry);
214
215 shadowOf(Looper.getMainLooper()).idle();
216
217 flutterRenderer.stopRenderingToSurface();
218
219 // Verify behavior under test.
220 verify(fakeFlutterJNI, times(1)).unregisterTexture(eq(id));
221 }
222
223 @Test
225 // Setup the test.
226 FlutterJNI fakeFlutterJNI = mock(FlutterJNI.class);
227 when(fakeFlutterJNI.isAttached()).thenReturn(false);
228 FlutterRenderer flutterRenderer = new FlutterRenderer(fakeFlutterJNI);
229
231
232 FlutterRenderer.SurfaceTextureRegistryEntry entry =
234 long id = entry.id();
235
236 flutterRenderer.startRenderingToSurface(fakeSurface, false);
237
238 flutterRenderer.stopRenderingToSurface();
239
240 // Execute the behavior under test.
241 runFinalization(entry);
242
243 shadowOf(Looper.getMainLooper()).idle();
244
245 // Verify behavior under test.
246 verify(fakeFlutterJNI, times(0)).unregisterTexture(eq(id));
247 }
248
250 CountDownLatch latch = new CountDownLatch(1);
251 Thread fakeFinalizer =
252 new Thread(
253 new Runnable() {
254 public void run() {
255 try {
256 entry.finalize();
257 latch.countDown();
258 } catch (Throwable e) {
259 // do nothing
260 }
261 }
262 });
263 fakeFinalizer.start();
264 try {
265 latch.await();
266 } catch (InterruptedException e) {
267 // do nothing
268 }
269 }
270
271 @Test
273 // Setup the test.
274 FlutterRenderer flutterRenderer = new FlutterRenderer(fakeFlutterJNI);
275 FlutterRenderer.ViewportMetrics metrics = new FlutterRenderer.ViewportMetrics();
276 metrics.width = 1000;
277 metrics.height = 1000;
278 metrics.devicePixelRatio = 2;
279 metrics.displayFeatures.add(
281 new Rect(10, 20, 30, 40),
284 metrics.displayFeatures.add(
286 new Rect(50, 60, 70, 80), FlutterRenderer.DisplayFeatureType.CUTOUT));
287
288 // Execute the behavior under test.
289 flutterRenderer.setViewportMetrics(metrics);
290
291 // Verify behavior under test.
292 ArgumentCaptor<int[]> boundsCaptor = ArgumentCaptor.forClass(int[].class);
293 ArgumentCaptor<int[]> typeCaptor = ArgumentCaptor.forClass(int[].class);
294 ArgumentCaptor<int[]> stateCaptor = ArgumentCaptor.forClass(int[].class);
295 verify(fakeFlutterJNI)
296 .setViewportMetrics(
297 anyFloat(),
298 anyInt(),
299 anyInt(),
300 anyInt(),
301 anyInt(),
302 anyInt(),
303 anyInt(),
304 anyInt(),
305 anyInt(),
306 anyInt(),
307 anyInt(),
308 anyInt(),
309 anyInt(),
310 anyInt(),
311 anyInt(),
312 anyInt(),
313 boundsCaptor.capture(),
314 typeCaptor.capture(),
315 stateCaptor.capture());
316
317 assertArrayEquals(new int[] {10, 20, 30, 40, 50, 60, 70, 80}, boundsCaptor.getValue());
318 assertArrayEquals(
319 new int[] {
321 FlutterRenderer.DisplayFeatureType.CUTOUT.encodedValue
322 },
323 typeCaptor.getValue());
324 assertArrayEquals(
325 new int[] {
327 FlutterRenderer.DisplayFeatureState.UNKNOWN.encodedValue
328 },
329 stateCaptor.getValue());
330 }
331
332 @Test
334 // Setup the test.
335 FlutterRenderer flutterRenderer = new FlutterRenderer(fakeFlutterJNI);
336
337 AtomicInteger invocationCount = new AtomicInteger(0);
338 final TextureRegistry.OnFrameConsumedListener listener =
339 new TextureRegistry.OnFrameConsumedListener() {
340 @Override
341 public void onFrameConsumed() {
342 invocationCount.incrementAndGet();
343 }
344 };
345
346 FlutterRenderer.SurfaceTextureRegistryEntry entry =
348 entry.setOnFrameConsumedListener(listener);
349
350 // Execute the behavior under test.
351 entry.textureWrapper().updateTexImage();
352
353 // Verify behavior under test.
354 assertEquals(1, invocationCount.get());
355 }
356
357 @Test
359 // Setup the test.
360 FlutterRenderer flutterRenderer = spy(new FlutterRenderer(fakeFlutterJNI));
361
362 // Execute the behavior under test.
363 FlutterRenderer.SurfaceTextureRegistryEntry entry =
365
366 // Verify behavior under test.
367 verify(flutterRenderer, times(1)).addOnTrimMemoryListener(entry);
368 }
369
370 @Test
372 // Setup the test.
373 FlutterRenderer flutterRenderer = spy(new FlutterRenderer(fakeFlutterJNI));
374 FlutterRenderer.SurfaceTextureRegistryEntry entry =
376
377 // Execute the behavior under test.
378 entry.release();
379
380 // Verify behavior under test.
381 verify(flutterRenderer, times(1)).removeOnTrimMemoryListener(entry);
382 }
383
384 @Test
386 // Setup the test.
387 FlutterRenderer flutterRenderer = new FlutterRenderer(fakeFlutterJNI);
388
389 AtomicInteger invocationCount = new AtomicInteger(0);
390 final TextureRegistry.OnTrimMemoryListener listener =
391 new TextureRegistry.OnTrimMemoryListener() {
392 @Override
393 public void onTrimMemory(int level) {
394 invocationCount.incrementAndGet();
395 }
396 };
397
398 FlutterRenderer.SurfaceTextureRegistryEntry entry =
400 entry.setOnTrimMemoryListener(listener);
401
402 // Execute the behavior under test.
403 flutterRenderer.onTrimMemory(TRIM_MEMORY_COMPLETE);
404
405 // Verify behavior under test.
406 assertEquals(1, invocationCount.get());
407 }
408
409 @Test
411 // Setup the test.
412 FlutterRenderer flutterRenderer = new FlutterRenderer(fakeFlutterJNI);
413
414 flutterRenderer.startRenderingToSurface(fakeSurface, false);
415
416 // Execute the behavior under test.
417 // Simulate calling |FlutterRenderer#stopRenderingToSurface| twice with different code paths.
418 flutterRenderer.stopRenderingToSurface();
419 flutterRenderer.stopRenderingToSurface();
420
421 // Verify behavior under test.
422 verify(fakeFlutterJNI, times(1)).onSurfaceDestroyed();
423 }
424
425 @Test
427 FlutterRenderer flutterRenderer = new FlutterRenderer(fakeFlutterJNI);
428
429 flutterRenderer.startRenderingToSurface(fakeSurface, false);
430 verify(fakeFlutterJNI, times(1)).onSurfaceCreated(eq(fakeSurface));
431 }
432
433 @Test
435 FlutterRenderer flutterRenderer = new FlutterRenderer(fakeFlutterJNI);
436
437 // The following call sequence mimics the behaviour of FlutterView when it exits from hybrid
438 // composition mode.
439
440 // Install initial rendering surface.
441 flutterRenderer.startRenderingToSurface(fakeSurface, false);
442 verify(fakeFlutterJNI, times(1)).onSurfaceCreated(eq(fakeSurface));
443 verify(fakeFlutterJNI, times(0)).onSurfaceWindowChanged(eq(fakeSurface));
444
445 // Install the image view.
446 flutterRenderer.startRenderingToSurface(fakeSurface2, true);
447 verify(fakeFlutterJNI, times(1)).onSurfaceCreated(eq(fakeSurface));
448 verify(fakeFlutterJNI, times(0)).onSurfaceWindowChanged(eq(fakeSurface));
449 verify(fakeFlutterJNI, times(0)).onSurfaceCreated(eq(fakeSurface2));
450 verify(fakeFlutterJNI, times(1)).onSurfaceWindowChanged(eq(fakeSurface2));
451
452 flutterRenderer.startRenderingToSurface(fakeSurface, true);
453 verify(fakeFlutterJNI, times(1)).onSurfaceCreated(eq(fakeSurface));
454 verify(fakeFlutterJNI, times(1)).onSurfaceWindowChanged(eq(fakeSurface));
455 verify(fakeFlutterJNI, times(0)).onSurfaceCreated(eq(fakeSurface2));
456 verify(fakeFlutterJNI, times(1)).onSurfaceWindowChanged(eq(fakeSurface2));
457 }
458
459 @Test
461 FlutterRenderer flutterRenderer = new FlutterRenderer(fakeFlutterJNI);
462 FlutterRenderer.ImageReaderSurfaceProducer texture =
463 flutterRenderer.new ImageReaderSurfaceProducer(0);
464 texture.disableFenceForTest();
465
466 // Returns a null image when one hasn't been produced.
467 assertNull(texture.acquireLatestImage());
468
469 // Give the texture an initial size.
470 texture.setSize(1, 1);
471
472 // Render a frame.
473 Surface surface = texture.getSurface();
474 assertNotNull(surface);
475 Canvas canvas = surface.lockHardwareCanvas();
476 canvas.drawARGB(255, 255, 0, 0);
477 surface.unlockCanvasAndPost(canvas);
478
479 // Let callbacks run.
480 shadowOf(Looper.getMainLooper()).idle();
481
482 // Extract the image and check its size.
483 Image image = texture.acquireLatestImage();
484 assertEquals(1, image.getWidth());
485 assertEquals(1, image.getHeight());
486 image.close();
487
488 // Resize the texture.
489 texture.setSize(5, 5);
490
491 // Render a frame.
492 surface = texture.getSurface();
493 assertNotNull(surface);
494 canvas = surface.lockHardwareCanvas();
495 canvas.drawARGB(255, 255, 0, 0);
496 surface.unlockCanvasAndPost(canvas);
497
498 // Let callbacks run.
499 shadowOf(Looper.getMainLooper()).idle();
500
501 // Extract the image and check its size.
502 image = texture.acquireLatestImage();
503 assertEquals(5, image.getWidth());
504 assertEquals(5, image.getHeight());
505 image.close();
506
507 assertNull(texture.acquireLatestImage());
508
509 texture.release();
510 }
511
512 @Test
514 FlutterRenderer flutterRenderer = new FlutterRenderer(fakeFlutterJNI);
515 FlutterRenderer.ImageReaderSurfaceProducer texture =
516 flutterRenderer.new ImageReaderSurfaceProducer(0);
517 texture.disableFenceForTest();
518
519 // Returns a null image when one hasn't been produced.
520 assertNull(texture.acquireLatestImage());
521
522 // Give the texture an initial size.
523 texture.setSize(1, 1);
524
525 // Render a frame.
526 Surface surface = texture.getSurface();
527 assertNotNull(surface);
528 Canvas canvas = surface.lockHardwareCanvas();
529 canvas.drawARGB(255, 255, 0, 0);
530 surface.unlockCanvasAndPost(canvas);
531
532 // Resize.
533 texture.setSize(4, 4);
534
535 // Let callbacks run. The rendered frame will manifest here.
536 shadowOf(Looper.getMainLooper()).idle();
537
538 // We acquired the frame produced above.
539 assertNotNull(texture.acquireLatestImage());
540 }
541
542 @Test
544 FlutterRenderer flutterRenderer = new FlutterRenderer(fakeFlutterJNI);
545 FlutterRenderer.ImageReaderSurfaceProducer texture =
546 flutterRenderer.new ImageReaderSurfaceProducer(0);
547 texture.disableFenceForTest();
548
549 // Returns a null image when one hasn't been produced.
550 assertNull(texture.acquireLatestImage());
551
552 // Give the texture an initial size.
553 texture.setSize(1, 1);
554
555 // Grab the surface so we can render a frame at 1x1 after resizing.
556 Surface surface = texture.getSurface();
557 assertNotNull(surface);
558 Canvas canvas = surface.lockHardwareCanvas();
559 canvas.drawARGB(255, 255, 0, 0);
560 surface.unlockCanvasAndPost(canvas);
561
562 // Let callbacks run, this will produce a single frame.
563 shadowOf(Looper.getMainLooper()).idle();
564
565 assertEquals(1, texture.numImageReaders());
566 assertEquals(1, texture.numImages());
567
568 // Resize.
569 texture.setSize(4, 4);
570
571 // Render a frame at the old size (by using the pre-resized Surface)
572 canvas = surface.lockHardwareCanvas();
573 canvas.drawARGB(255, 255, 0, 0);
574 surface.unlockCanvasAndPost(canvas);
575
576 // Let callbacks run.
577 shadowOf(Looper.getMainLooper()).idle();
578
579 assertEquals(1, texture.numImageReaders());
580 assertEquals(2, texture.numImages());
581
582 // Render a new frame with the current size.
583 surface = texture.getSurface();
584 assertNotNull(surface);
585 canvas = surface.lockHardwareCanvas();
586 canvas.drawARGB(255, 255, 0, 0);
587 surface.unlockCanvasAndPost(canvas);
588
589 // Let callbacks run.
590 shadowOf(Looper.getMainLooper()).idle();
591
592 assertEquals(2, texture.numImageReaders());
593 assertEquals(3, texture.numImages());
594
595 // Acquire first frame.
596 Image produced = texture.acquireLatestImage();
597 assertNotNull(produced);
598 assertEquals(1, produced.getWidth());
599 assertEquals(1, produced.getHeight());
600 assertEquals(2, texture.numImageReaders());
601 assertEquals(2, texture.numImages());
602 // Acquire second frame. This won't result in the first reader being closed because it has
603 // an active image from it.
604 produced = texture.acquireLatestImage();
605 assertNotNull(produced);
606 assertEquals(1, produced.getWidth());
607 assertEquals(1, produced.getHeight());
608 assertEquals(2, texture.numImageReaders());
609 assertEquals(1, texture.numImages());
610 // Acquire third frame. We will now close the first reader.
611 produced = texture.acquireLatestImage();
612 assertNotNull(produced);
613 assertEquals(4, produced.getWidth());
614 assertEquals(4, produced.getHeight());
615 assertEquals(1, texture.numImageReaders());
616 assertEquals(0, texture.numImages());
617
618 // Returns null image when no more images are queued.
619 assertNull(texture.acquireLatestImage());
620 assertEquals(1, texture.numImageReaders());
621 assertEquals(0, texture.numImages());
622 }
623
624 @Test
626 FlutterRenderer flutterRenderer = new FlutterRenderer(fakeFlutterJNI);
627 FlutterRenderer.ImageReaderSurfaceProducer texture =
628 flutterRenderer.new ImageReaderSurfaceProducer(0);
629 texture.disableFenceForTest();
630
631 // Returns a null image when one hasn't been produced.
632 assertNull(texture.acquireLatestImage());
633
634 // Give the texture an initial size.
635 texture.setSize(1, 1);
636
637 // Grab the surface so we can render a frame at 1x1 after resizing.
638 Surface surface = texture.getSurface();
639 assertNotNull(surface);
640 Canvas canvas = surface.lockHardwareCanvas();
641 canvas.drawARGB(255, 255, 0, 0);
642 surface.unlockCanvasAndPost(canvas);
643
644 // Let callbacks run, this will produce a single frame.
645 shadowOf(Looper.getMainLooper()).idle();
646
647 assertEquals(1, texture.numImageReaders());
648 assertEquals(1, texture.numImages());
649
650 // Invoke the onTrimMemory callback.
651 // This should do nothing.
652 texture.onTrimMemory(0);
653 shadowOf(Looper.getMainLooper()).idle();
654
655 assertEquals(1, texture.numImageReaders());
656 assertEquals(1, texture.numImages());
657 }
658
659 // A 0x0 ImageReader is a runtime error.
660 @Test
662 FlutterRenderer flutterRenderer = new FlutterRenderer(fakeFlutterJNI);
663 FlutterRenderer.ImageReaderSurfaceProducer texture =
664 flutterRenderer.new ImageReaderSurfaceProducer(0);
665
666 // Default values.
667 assertEquals(texture.getWidth(), 1);
668 assertEquals(texture.getHeight(), 1);
669
670 // Try setting width and height to 0.
671 texture.setSize(0, 0);
672
673 // Ensure we can still create/get a surface without an exception being raised.
674 assertNotNull(texture.getSurface());
675
676 // Expect clamp to 1.
677 assertEquals(texture.getWidth(), 1);
678 assertEquals(texture.getHeight(), 1);
679 }
680
681 @Test
683 // Force creating a SurfaceTextureSurfaceProducer regardless of Android API version.
684 FlutterRenderer.debugForceSurfaceProducerGlTextures = true;
685
686 FlutterRenderer flutterRenderer = new FlutterRenderer(fakeFlutterJNI);
687 TextureRegistry.SurfaceProducer producer = flutterRenderer.createSurfaceProducer();
688
689 flutterRenderer.startRenderingToSurface(fakeSurface, false);
690
691 // Verify behavior under test.
692 assertEquals(producer.id(), 0);
693 verify(fakeFlutterJNI, times(1)).registerTexture(eq(producer.id()), any());
694 }
695}
static SkISize times(const SkISize &size, float factor)
static bool eq(const SkM44 &a, const SkM44 &b, float tol)
Definition M44Test.cpp:18
void runFinalization(FlutterRenderer.SurfaceTextureRegistryEntry entry)
SurfaceTextureEntry registerSurfaceTexture(@NonNull SurfaceTexture surfaceTexture)
void startRenderingToSurface(@NonNull Surface surface, boolean onlySwap)
void setViewportMetrics(@NonNull ViewportMetrics viewportMetrics)
VkSurfaceKHR surface
Definition main.cc:49
sk_sp< SkImage > image
Definition examples.cpp:29
FlTexture * texture
Definition run.py:1