Skip to content

Commit 96dffbe

Browse files
author
Jonah Williams
authored
[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 flutter/flutter#159688 ## Before ![flutter_04](https://github.com/user-attachments/assets/be60b1d0-4e94-491c-b1da-a03d66897f12) ## After ![flutter_03](https://github.com/user-attachments/assets/64cca8b7-3ae7-4f40-af26-ae61d9ec6290)
1 parent a38abc8 commit 96dffbe

File tree

5 files changed

+211
-5
lines changed

5 files changed

+211
-5
lines changed

dev/integration_tests/android_engine_test/android/app/src/main/kotlin/com/example/native_driver_test/MainActivity.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import androidx.core.view.WindowInsetsControllerCompat
1313
import com.example.android_engine_test.extensions.NativeDriverSupportPlugin
1414
import com.example.android_engine_test.fixtures.BlueOrangeGradientPlatformViewFactory
1515
import com.example.android_engine_test.fixtures.ChangingColorButtonPlatformViewFactory
16+
import com.example.android_engine_test.fixtures.OtherFaceTexturePlugin
1617
import com.example.android_engine_test.fixtures.SmileyFaceTexturePlugin
1718
import io.flutter.embedding.android.FlutterActivity
1819
import io.flutter.embedding.engine.FlutterEngine
@@ -25,6 +26,7 @@ class MainActivity : FlutterActivity() {
2526
.plugins
2627
.apply {
2728
add(SmileyFaceTexturePlugin())
29+
add(OtherFaceTexturePlugin())
2830
add(NativeDriverSupportPlugin())
2931
}
3032

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
// Copyright 2014 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+
5+
@file:Suppress("PackageName")
6+
7+
package com.example.android_engine_test.fixtures
8+
9+
import android.graphics.Color
10+
import android.graphics.Paint
11+
import android.os.Build
12+
import android.view.Surface
13+
import io.flutter.embedding.engine.plugins.FlutterPlugin
14+
import io.flutter.embedding.engine.plugins.FlutterPlugin.FlutterPluginBinding
15+
import io.flutter.plugin.common.MethodCall
16+
import io.flutter.plugin.common.MethodChannel
17+
import io.flutter.plugin.common.MethodChannel.MethodCallHandler
18+
import io.flutter.view.TextureRegistry.SurfaceTextureEntry
19+
20+
class OtherFaceTexturePlugin :
21+
FlutterPlugin,
22+
MethodCallHandler {
23+
private val tag = "OtherFaceTexturePlugin"
24+
private lateinit var channel: MethodChannel
25+
private lateinit var binding: FlutterPluginBinding
26+
private lateinit var surfaceTextureEntry: SurfaceTextureEntry
27+
28+
private var surface: Surface? = null
29+
30+
override fun onAttachedToEngine(binding: FlutterPluginBinding) {
31+
this.binding = binding
32+
channel = MethodChannel(binding.binaryMessenger, "other_face_texture")
33+
channel.setMethodCallHandler(this)
34+
surfaceTextureEntry = binding.textureRegistry.createSurfaceTexture()
35+
}
36+
37+
override fun onDetachedFromEngine(binding: FlutterPluginBinding) {
38+
channel.setMethodCallHandler(null)
39+
surfaceTextureEntry.release()
40+
}
41+
42+
override fun onMethodCall(
43+
call: MethodCall,
44+
result: MethodChannel.Result
45+
) {
46+
if (call.method == "initTexture") {
47+
val height = call.argument<Int>("height") ?: 1
48+
val width = call.argument<Int>("width") ?: 1
49+
surfaceTextureEntry.surfaceTexture().setDefaultBufferSize(width, height)
50+
result.success(updateTexture())
51+
} else {
52+
result.notImplemented()
53+
}
54+
}
55+
56+
private fun updateTexture(): Long {
57+
var surface = this.surface
58+
if (surface == null) {
59+
surface = Surface(surfaceTextureEntry.surfaceTexture())
60+
this.surface = surface
61+
}
62+
drawOnSurface(surface!!)
63+
return surfaceTextureEntry.id()
64+
}
65+
66+
private fun drawOnSurface(surface: Surface) {
67+
val canvas =
68+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
69+
surface.lockHardwareCanvas()
70+
} else {
71+
surface.lockCanvas(null)
72+
}
73+
74+
// Yellow background
75+
canvas.drawRGB(255, 230, 15)
76+
77+
val paint = Paint()
78+
paint.style = Paint.Style.FILL
79+
80+
// Black eyes
81+
paint.color = Color.BLACK
82+
canvas.drawCircle(225f, 225f, 25f, paint) // Left eye
83+
canvas.drawCircle(425f, 225f, 25f, paint) // Right eye
84+
85+
// Black mouth
86+
paint.color = Color.BLACK
87+
canvas.drawCircle(300f, 300f, 50f, paint) // Simple mouth
88+
89+
surface.unlockCanvasAndPost(canvas)
90+
}
91+
}
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
// Copyright 2014 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+
5+
import 'dart:async';
6+
7+
import 'package:android_driver_extensions/extension.dart';
8+
import 'package:flutter/material.dart';
9+
import 'package:flutter/services.dart';
10+
import 'package:flutter_driver/driver_extension.dart';
11+
12+
import 'src/allow_list_devices.dart';
13+
14+
const MethodChannel _channel = MethodChannel('other_face_texture');
15+
Future<int> _fetchTexture(int width, int height) async {
16+
final int? result = await _channel.invokeMethod<int>('initTexture', <String, int>{
17+
'width': width,
18+
'height': height,
19+
});
20+
return result!;
21+
}
22+
23+
void main() async {
24+
ensureAndroidOrIosDevice();
25+
enableFlutterDriverExtension(commands: <CommandExtension>[nativeDriverCommands]);
26+
27+
// Run on full screen.
28+
SystemChrome.setEnabledSystemUIMode(SystemUiMode.immersive);
29+
30+
// Fetch the texture ID.
31+
final Future<int> textureId = _fetchTexture(512, 512);
32+
runApp(MainApp(textureId));
33+
}
34+
35+
final class MainApp extends StatelessWidget {
36+
const MainApp(this.textureId, {super.key});
37+
final Future<int> textureId;
38+
39+
@override
40+
Widget build(BuildContext context) {
41+
return MaterialApp(
42+
debugShowCheckedModeBanner: false,
43+
home: FutureBuilder<int>(
44+
future: textureId,
45+
builder: (BuildContext context, AsyncSnapshot<int> snapshot) {
46+
if (snapshot.hasData) {
47+
return Texture(textureId: snapshot.data!);
48+
}
49+
return const CircularProgressIndicator();
50+
},
51+
),
52+
);
53+
}
54+
}
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
// Copyright 2014 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+
5+
import 'package:android_driver_extensions/native_driver.dart';
6+
import 'package:android_driver_extensions/skia_gold.dart';
7+
import 'package:flutter_driver/flutter_driver.dart';
8+
import 'package:test/test.dart';
9+
10+
import '_luci_skia_gold_prelude.dart';
11+
12+
void main() async {
13+
// To test the golden file generation locally, comment out the following line.
14+
// autoUpdateGoldenFiles = true;
15+
16+
const String appName = 'com.example.android_engine_test';
17+
late final FlutterDriver flutterDriver;
18+
late final NativeDriver nativeDriver;
19+
20+
setUpAll(() async {
21+
if (isLuci) {
22+
await enableSkiaGoldComparator();
23+
}
24+
flutterDriver = await FlutterDriver.connect();
25+
nativeDriver = await AndroidNativeDriver.connect(flutterDriver);
26+
await nativeDriver.configureForScreenshotTesting();
27+
});
28+
29+
tearDownAll(() async {
30+
await nativeDriver.close();
31+
await flutterDriver.close();
32+
});
33+
34+
test('should screenshot and match an external smiley face texture', () async {
35+
await flutterDriver.waitFor(find.byType('Texture'));
36+
37+
// On Android: Background the app, trim memory, and restore the app.
38+
if (nativeDriver case final AndroidNativeDriver nativeDriver) {
39+
print('Backgrounding the app, trimming memory, and resuming the app.');
40+
await nativeDriver.backgroundApp();
41+
42+
print('Trimming memory.');
43+
await nativeDriver.simulateLowMemory(appName: appName);
44+
45+
print('Resuming the app.');
46+
await nativeDriver.resumeApp(appName: appName);
47+
}
48+
49+
await expectLater(
50+
nativeDriver.screenshot(),
51+
matchesGoldenFile('external_texture_other_face.android.png'),
52+
);
53+
}, timeout: Timeout.none);
54+
}

engine/src/flutter/shell/platform/android/surface_texture_external_texture_vk_impeller.cc

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -122,15 +122,17 @@ void SurfaceTextureExternalTextureVKImpeller::ProcessFrame(
122122
VALIDATION_LOG << "Invalid external texture.";
123123
return;
124124
}
125+
SkMatrix matrix = context.canvas->GetTransform();
126+
SkRect mapped_bounds = matrix.mapRect(bounds);
125127

126128
const auto& surface_context =
127129
SurfaceContextVK::Cast(*context.aiks_context->GetContext());
128130
const auto& context_vk = ContextVK::Cast(*surface_context.GetParent());
129131

130-
auto dst_texture =
131-
GetCachedTextureSource(surface_context.GetParent(), //
132-
ISize::MakeWH(bounds.width(), bounds.height()) //
133-
);
132+
auto dst_texture = GetCachedTextureSource(
133+
surface_context.GetParent(), //
134+
ISize::MakeWH(mapped_bounds.width(), mapped_bounds.height()) //
135+
);
134136
if (!dst_texture || !dst_texture->IsValid()) {
135137
VALIDATION_LOG << "Could not fetch trampoline texture target.";
136138
return;
@@ -224,7 +226,10 @@ void SurfaceTextureExternalTextureVKImpeller::DrawFrame(
224226
PaintContext& context,
225227
const SkRect& bounds,
226228
const DlImageSampling sampling) const {
227-
context.canvas->DrawImage(dl_image_, SkPoint{0, 0}, sampling, context.paint);
229+
context.canvas->DrawImageRect(
230+
dl_image_,
231+
SkRect::MakeSize(SkSize::Make(dl_image_->width(), dl_image_->height())),
232+
bounds, sampling, context.paint);
228233
}
229234

230235
} // namespace flutter

0 commit comments

Comments
 (0)