[Impeller] fix scaling of trampoline import of GLES textures into Vulkan. (#161331)
Not sure if this just needs the dpr transform or the entire canvas
transform.
Fixes https://github.com/flutter/flutter/issues/159688
## Before

## After

diff --git a/dev/integration_tests/android_engine_test/android/app/src/main/kotlin/com/example/native_driver_test/MainActivity.kt b/dev/integration_tests/android_engine_test/android/app/src/main/kotlin/com/example/native_driver_test/MainActivity.kt
index 807b51c..6b74332 100644
--- a/dev/integration_tests/android_engine_test/android/app/src/main/kotlin/com/example/native_driver_test/MainActivity.kt
+++ b/dev/integration_tests/android_engine_test/android/app/src/main/kotlin/com/example/native_driver_test/MainActivity.kt
@@ -13,6 +13,7 @@
import com.example.android_engine_test.extensions.NativeDriverSupportPlugin
import com.example.android_engine_test.fixtures.BlueOrangeGradientPlatformViewFactory
import com.example.android_engine_test.fixtures.ChangingColorButtonPlatformViewFactory
+import com.example.android_engine_test.fixtures.OtherFaceTexturePlugin
import com.example.android_engine_test.fixtures.SmileyFaceTexturePlugin
import io.flutter.embedding.android.FlutterActivity
import io.flutter.embedding.engine.FlutterEngine
@@ -25,6 +26,7 @@
.plugins
.apply {
add(SmileyFaceTexturePlugin())
+ add(OtherFaceTexturePlugin())
add(NativeDriverSupportPlugin())
}
diff --git a/dev/integration_tests/android_engine_test/android/app/src/main/kotlin/com/example/native_driver_test/fixtures/OtherFaceTexturePlugin.kt b/dev/integration_tests/android_engine_test/android/app/src/main/kotlin/com/example/native_driver_test/fixtures/OtherFaceTexturePlugin.kt
new file mode 100644
index 0000000..24cb3a2
--- /dev/null
+++ b/dev/integration_tests/android_engine_test/android/app/src/main/kotlin/com/example/native_driver_test/fixtures/OtherFaceTexturePlugin.kt
@@ -0,0 +1,91 @@
+// Copyright 2014 The Flutter Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+@file:Suppress("PackageName")
+
+package com.example.android_engine_test.fixtures
+
+import android.graphics.Color
+import android.graphics.Paint
+import android.os.Build
+import android.view.Surface
+import io.flutter.embedding.engine.plugins.FlutterPlugin
+import io.flutter.embedding.engine.plugins.FlutterPlugin.FlutterPluginBinding
+import io.flutter.plugin.common.MethodCall
+import io.flutter.plugin.common.MethodChannel
+import io.flutter.plugin.common.MethodChannel.MethodCallHandler
+import io.flutter.view.TextureRegistry.SurfaceTextureEntry
+
+class OtherFaceTexturePlugin :
+ FlutterPlugin,
+ MethodCallHandler {
+ private val tag = "OtherFaceTexturePlugin"
+ private lateinit var channel: MethodChannel
+ private lateinit var binding: FlutterPluginBinding
+ private lateinit var surfaceTextureEntry: SurfaceTextureEntry
+
+ private var surface: Surface? = null
+
+ override fun onAttachedToEngine(binding: FlutterPluginBinding) {
+ this.binding = binding
+ channel = MethodChannel(binding.binaryMessenger, "other_face_texture")
+ channel.setMethodCallHandler(this)
+ surfaceTextureEntry = binding.textureRegistry.createSurfaceTexture()
+ }
+
+ override fun onDetachedFromEngine(binding: FlutterPluginBinding) {
+ channel.setMethodCallHandler(null)
+ surfaceTextureEntry.release()
+ }
+
+ override fun onMethodCall(
+ call: MethodCall,
+ result: MethodChannel.Result
+ ) {
+ if (call.method == "initTexture") {
+ val height = call.argument<Int>("height") ?: 1
+ val width = call.argument<Int>("width") ?: 1
+ surfaceTextureEntry.surfaceTexture().setDefaultBufferSize(width, height)
+ result.success(updateTexture())
+ } else {
+ result.notImplemented()
+ }
+ }
+
+ private fun updateTexture(): Long {
+ var surface = this.surface
+ if (surface == null) {
+ surface = Surface(surfaceTextureEntry.surfaceTexture())
+ this.surface = surface
+ }
+ drawOnSurface(surface!!)
+ return surfaceTextureEntry.id()
+ }
+
+ private fun drawOnSurface(surface: Surface) {
+ val canvas =
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
+ surface.lockHardwareCanvas()
+ } else {
+ surface.lockCanvas(null)
+ }
+
+ // Yellow background
+ canvas.drawRGB(255, 230, 15)
+
+ val paint = Paint()
+ paint.style = Paint.Style.FILL
+
+ // Black eyes
+ paint.color = Color.BLACK
+ canvas.drawCircle(225f, 225f, 25f, paint) // Left eye
+ canvas.drawCircle(425f, 225f, 25f, paint) // Right eye
+
+ // Black mouth
+ paint.color = Color.BLACK
+ canvas.drawCircle(300f, 300f, 50f, paint) // Simple mouth
+
+ surface.unlockCanvasAndPost(canvas)
+ }
+}
diff --git a/dev/integration_tests/android_engine_test/lib/external_texture_other_face_main.dart b/dev/integration_tests/android_engine_test/lib/external_texture_other_face_main.dart
new file mode 100644
index 0000000..3df4b89
--- /dev/null
+++ b/dev/integration_tests/android_engine_test/lib/external_texture_other_face_main.dart
@@ -0,0 +1,54 @@
+// Copyright 2014 The Flutter Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import 'dart:async';
+
+import 'package:android_driver_extensions/extension.dart';
+import 'package:flutter/material.dart';
+import 'package:flutter/services.dart';
+import 'package:flutter_driver/driver_extension.dart';
+
+import 'src/allow_list_devices.dart';
+
+const MethodChannel _channel = MethodChannel('other_face_texture');
+Future<int> _fetchTexture(int width, int height) async {
+ final int? result = await _channel.invokeMethod<int>('initTexture', <String, int>{
+ 'width': width,
+ 'height': height,
+ });
+ return result!;
+}
+
+void main() async {
+ ensureAndroidOrIosDevice();
+ enableFlutterDriverExtension(commands: <CommandExtension>[nativeDriverCommands]);
+
+ // Run on full screen.
+ SystemChrome.setEnabledSystemUIMode(SystemUiMode.immersive);
+
+ // Fetch the texture ID.
+ final Future<int> textureId = _fetchTexture(512, 512);
+ runApp(MainApp(textureId));
+}
+
+final class MainApp extends StatelessWidget {
+ const MainApp(this.textureId, {super.key});
+ final Future<int> textureId;
+
+ @override
+ Widget build(BuildContext context) {
+ return MaterialApp(
+ debugShowCheckedModeBanner: false,
+ home: FutureBuilder<int>(
+ future: textureId,
+ builder: (BuildContext context, AsyncSnapshot<int> snapshot) {
+ if (snapshot.hasData) {
+ return Texture(textureId: snapshot.data!);
+ }
+ return const CircularProgressIndicator();
+ },
+ ),
+ );
+ }
+}
diff --git a/dev/integration_tests/android_engine_test/test_driver/external_texture_other_face_main_test.dart b/dev/integration_tests/android_engine_test/test_driver/external_texture_other_face_main_test.dart
new file mode 100644
index 0000000..4ea49c1
--- /dev/null
+++ b/dev/integration_tests/android_engine_test/test_driver/external_texture_other_face_main_test.dart
@@ -0,0 +1,54 @@
+// Copyright 2014 The Flutter Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import 'package:android_driver_extensions/native_driver.dart';
+import 'package:android_driver_extensions/skia_gold.dart';
+import 'package:flutter_driver/flutter_driver.dart';
+import 'package:test/test.dart';
+
+import '_luci_skia_gold_prelude.dart';
+
+void main() async {
+ // To test the golden file generation locally, comment out the following line.
+ // autoUpdateGoldenFiles = true;
+
+ const String appName = 'com.example.android_engine_test';
+ late final FlutterDriver flutterDriver;
+ late final NativeDriver nativeDriver;
+
+ setUpAll(() async {
+ if (isLuci) {
+ await enableSkiaGoldComparator();
+ }
+ flutterDriver = await FlutterDriver.connect();
+ nativeDriver = await AndroidNativeDriver.connect(flutterDriver);
+ await nativeDriver.configureForScreenshotTesting();
+ });
+
+ tearDownAll(() async {
+ await nativeDriver.close();
+ await flutterDriver.close();
+ });
+
+ test('should screenshot and match an external smiley face texture', () async {
+ await flutterDriver.waitFor(find.byType('Texture'));
+
+ // On Android: Background the app, trim memory, and restore the app.
+ if (nativeDriver case final AndroidNativeDriver nativeDriver) {
+ print('Backgrounding the app, trimming memory, and resuming the app.');
+ await nativeDriver.backgroundApp();
+
+ print('Trimming memory.');
+ await nativeDriver.simulateLowMemory(appName: appName);
+
+ print('Resuming the app.');
+ await nativeDriver.resumeApp(appName: appName);
+ }
+
+ await expectLater(
+ nativeDriver.screenshot(),
+ matchesGoldenFile('external_texture_other_face.android.png'),
+ );
+ }, timeout: Timeout.none);
+}
diff --git a/engine/src/flutter/shell/platform/android/surface_texture_external_texture_vk_impeller.cc b/engine/src/flutter/shell/platform/android/surface_texture_external_texture_vk_impeller.cc
index eb144e8..0cf96aa 100644
--- a/engine/src/flutter/shell/platform/android/surface_texture_external_texture_vk_impeller.cc
+++ b/engine/src/flutter/shell/platform/android/surface_texture_external_texture_vk_impeller.cc
@@ -122,15 +122,17 @@
VALIDATION_LOG << "Invalid external texture.";
return;
}
+ SkMatrix matrix = context.canvas->GetTransform();
+ SkRect mapped_bounds = matrix.mapRect(bounds);
const auto& surface_context =
SurfaceContextVK::Cast(*context.aiks_context->GetContext());
const auto& context_vk = ContextVK::Cast(*surface_context.GetParent());
- auto dst_texture =
- GetCachedTextureSource(surface_context.GetParent(), //
- ISize::MakeWH(bounds.width(), bounds.height()) //
- );
+ auto dst_texture = GetCachedTextureSource(
+ surface_context.GetParent(), //
+ ISize::MakeWH(mapped_bounds.width(), mapped_bounds.height()) //
+ );
if (!dst_texture || !dst_texture->IsValid()) {
VALIDATION_LOG << "Could not fetch trampoline texture target.";
return;
@@ -224,7 +226,10 @@
PaintContext& context,
const SkRect& bounds,
const DlImageSampling sampling) const {
- context.canvas->DrawImage(dl_image_, SkPoint{0, 0}, sampling, context.paint);
+ context.canvas->DrawImageRect(
+ dl_image_,
+ SkRect::MakeSize(SkSize::Make(dl_image_->width(), dl_image_->height())),
+ bounds, sampling, context.paint);
}
} // namespace flutter