diff --git a/thermion_flutter/thermion_flutter/lib/src/platform/src/method_channel_platform_texture_descriptor.dart b/thermion_flutter/thermion_flutter/lib/src/platform/src/method_channel_platform_texture_descriptor.dart index d35ce6ef0..ccbd8b835 100644 --- a/thermion_flutter/thermion_flutter/lib/src/platform/src/method_channel_platform_texture_descriptor.dart +++ b/thermion_flutter/thermion_flutter/lib/src/platform/src/method_channel_platform_texture_descriptor.dart @@ -12,6 +12,7 @@ class MethodChannelPlatformTextureDescriptor extends PlatformTextureDescriptor { required super.windowHandle, required super.width, required super.height, + super.flipVertically, bool deferred = false, }) : _deferred = deferred; @@ -38,6 +39,7 @@ class MethodChannelPlatformTextureDescriptor extends PlatformTextureDescriptor { int width, int height, { bool deferred = false, + bool flipVertically = false, }) async { final allocation = await allocateMethodChannelTexture( channel, @@ -52,6 +54,7 @@ class MethodChannelPlatformTextureDescriptor extends PlatformTextureDescriptor { width: width, height: height, deferred: deferred, + flipVertically: flipVertically, ); } diff --git a/thermion_flutter/thermion_flutter/lib/src/platform/src/native_texture_surface_manager.dart b/thermion_flutter/thermion_flutter/lib/src/platform/src/native_texture_surface_manager.dart index 1e4b26348..c075f758c 100644 --- a/thermion_flutter/thermion_flutter/lib/src/platform/src/native_texture_surface_manager.dart +++ b/thermion_flutter/thermion_flutter/lib/src/platform/src/native_texture_surface_manager.dart @@ -62,6 +62,7 @@ class NativeTextureSurfaceManager { resumeRenderingIfReady: lifecycle.resumeRenderingIfReady, runTextureMutation: lifecycle.duringTextureMutation, androidTextureSource: () => options().androidTextureSource, + flipLinuxTextureVertically: () => options().backend != Backend.VULKAN, ); static final _logger = Logger('NativeTextureSurfaceManager'); diff --git a/thermion_flutter/thermion_flutter/lib/src/platform/src/platform_texture_descriptor.dart b/thermion_flutter/thermion_flutter/lib/src/platform/src/platform_texture_descriptor.dart index d2544070b..715258ed9 100644 --- a/thermion_flutter/thermion_flutter/lib/src/platform/src/platform_texture_descriptor.dart +++ b/thermion_flutter/thermion_flutter/lib/src/platform/src/platform_texture_descriptor.dart @@ -11,6 +11,12 @@ abstract class PlatformTextureDescriptor { int height; + /// Whether Flutter must invert this texture vertically when presenting it. + /// + /// OpenGL render targets use a bottom-left origin, while Flutter's widget + /// coordinate system uses a top-left origin. + final bool flipVertically; + View? _boundView; /// The (possibly null) view this descriptor is bound to @@ -28,6 +34,7 @@ abstract class PlatformTextureDescriptor { required this.width, required this.height, this.windowHandle, + this.flipVertically = false, }); /// Descriptors are identified by their Flutter texture id. This is stable diff --git a/thermion_flutter/thermion_flutter/lib/src/platform/src/platform_texture_descriptor_registry_native.dart b/thermion_flutter/thermion_flutter/lib/src/platform/src/platform_texture_descriptor_registry_native.dart index 6e4bdc2d0..1533228e4 100644 --- a/thermion_flutter/thermion_flutter/lib/src/platform/src/platform_texture_descriptor_registry_native.dart +++ b/thermion_flutter/thermion_flutter/lib/src/platform/src/platform_texture_descriptor_registry_native.dart @@ -39,12 +39,17 @@ class NativePlatformTextureDescriptorRegistry required void Function() resumeRenderingIfReady, required TextureMutationRunner runTextureMutation, required AndroidTextureSource Function() androidTextureSource, + required bool Function() flipLinuxTextureVertically, }) : _pauseRendering = pauseRendering, _resumeRenderingIfReady = resumeRenderingIfReady, _runTextureMutation = runTextureMutation, super( - allocator: (width, height) => - _allocate(width, height, androidTextureSource()), + allocator: (width, height) => _allocate( + width, + height, + androidTextureSource(), + flipLinuxTextureVertically(), + ), ) { if (Platform.isAndroid) { channel.setMethodCallHandler(_handlePlatformMethodCall); @@ -62,6 +67,7 @@ class NativePlatformTextureDescriptorRegistry int width, int height, AndroidTextureSource androidTextureSource, + bool flipLinuxTextureVertically, ) { if (Platform.isMacOS || Platform.isIOS) { return Future.value( @@ -89,6 +95,7 @@ class NativePlatformTextureDescriptorRegistry width, height, deferred: true, + flipVertically: flipLinuxTextureVertically, ); } throw UnsupportedError('Platform textures are not supported on $Platform'); diff --git a/thermion_flutter/thermion_flutter/lib/src/widgets/src/thermion_widget_internal/texture_widget_builder.dart b/thermion_flutter/thermion_flutter/lib/src/widgets/src/thermion_widget_internal/texture_widget_builder.dart index 33145bd84..cec18cc69 100644 --- a/thermion_flutter/thermion_flutter/lib/src/widgets/src/thermion_widget_internal/texture_widget_builder.dart +++ b/thermion_flutter/thermion_flutter/lib/src/widgets/src/thermion_widget_internal/texture_widget_builder.dart @@ -7,10 +7,17 @@ Widget surfaceWidgetBuilder(PlatformTextureDescriptor? descriptor, View view) { if (descriptor == null) { return const SizedBox.shrink(); } - return Texture( + return buildTextureWidget(descriptor); +} + +@visibleForTesting +Widget buildTextureWidget(PlatformTextureDescriptor descriptor) { + final texture = Texture( key: ObjectKey("flutter_texture_${descriptor.flutterTextureId}"), textureId: descriptor.flutterTextureId, filterQuality: FilterQuality.none, freeze: false, ); + if (!descriptor.flipVertically) return texture; + return Transform.flip(flipY: true, child: texture); } diff --git a/thermion_flutter/thermion_flutter/test/thermion_widget_resize_transition_test.dart b/thermion_flutter/thermion_flutter/test/thermion_widget_resize_transition_test.dart index fdc079ff1..d27858abe 100644 --- a/thermion_flutter/thermion_flutter/test/thermion_widget_resize_transition_test.dart +++ b/thermion_flutter/thermion_flutter/test/thermion_widget_resize_transition_test.dart @@ -1,6 +1,8 @@ import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; +import 'package:thermion_flutter/src/platform/src/platform_texture_descriptor.dart'; import 'package:thermion_flutter/src/widgets/src/thermion_widget.dart'; +import 'package:thermion_flutter/src/widgets/src/thermion_widget_internal/texture_widget_builder.dart'; void main() { testWidgets('staged replacement is mounted below the last valid frame', ( @@ -48,4 +50,45 @@ void main() { expect(find.byKey(currentKey), findsOneWidget); expect(find.byType(Stack), findsNothing); }); + + testWidgets('flagged platform texture is vertically flipped', (tester) async { + await tester.pumpWidget( + MaterialApp( + home: buildTextureWidget(_TextureDescriptor(flipVertically: true)), + ), + ); + + final transform = tester.widget(find.byType(Transform)); + expect(transform.transform.entry(0, 0), 1); + expect(transform.transform.entry(1, 1), -1); + expect(find.byType(Texture), findsOneWidget); + }); + + testWidgets('unflagged platform texture is not transformed', (tester) async { + await tester.pumpWidget( + MaterialApp( + home: buildTextureWidget(_TextureDescriptor(flipVertically: false)), + ), + ); + + expect(find.byType(Transform), findsNothing); + expect(find.byType(Texture), findsOneWidget); + }); +} + +class _TextureDescriptor extends PlatformTextureDescriptor { + _TextureDescriptor({required bool flipVertically}) + : super( + flutterTextureId: 1, + hardwareId: 2, + width: 3, + height: 4, + flipVertically: flipVertically, + ); + + @override + Future destroy() async {} + + @override + Future markTextureFrameAvailable() async {} }