diff --git a/src/SamplesApp/SamplesApp.UITests/Windows_UI_Xaml_Controls/SwipeControlTests/SwipeControlTests.cs b/src/SamplesApp/SamplesApp.UITests/Windows_UI_Xaml_Controls/SwipeControlTests/SwipeControlTests.cs index 38578810a941..dcdadeb86a3a 100644 --- a/src/SamplesApp/SamplesApp.UITests/Windows_UI_Xaml_Controls/SwipeControlTests/SwipeControlTests.cs +++ b/src/SamplesApp/SamplesApp.UITests/Windows_UI_Xaml_Controls/SwipeControlTests/SwipeControlTests.cs @@ -72,6 +72,8 @@ public async Task When_MultipleItems() [ActivePlatforms(Platform.iOS, Platform.Android)] #if __SKIA__ [Ignore("Invalid layout of items")] +#elif !HAS_RENDER_TARGET_BITMAP + [Ignore("Test relies on screenshots which is not available on this platform.")] #endif public Task When_InListView() => When_InScrollableContainer("UITests.Windows_UI_Xaml_Controls.SwipeControlTests.SwipeControl_ListView"); @@ -79,6 +81,11 @@ public Task When_InListView() [Test] [AutoRetry] [ActivePlatforms(Platform.iOS, Platform.Android)] +#if __SKIA__ + [Ignore("Invalid layout of items")] +#elif !HAS_RENDER_TARGET_BITMAP + [Ignore("Test relies on screenshots which is not available on this platform.")] +#endif public Task When_InScrollViewer() => When_InScrollableContainer("UITests.Windows_UI_Xaml_Controls.SwipeControlTests.SwipeControl_ScrollViewer"); diff --git a/src/SamplesApp/SamplesApp.UITests/Windows_UI_Xaml_Input/Capture_Tests.cs b/src/SamplesApp/SamplesApp.UITests/Windows_UI_Xaml_Input/Capture_Tests.cs index 00cb26a099bf..165414f207b0 100644 --- a/src/SamplesApp/SamplesApp.UITests/Windows_UI_Xaml_Input/Capture_Tests.cs +++ b/src/SamplesApp/SamplesApp.UITests/Windows_UI_Xaml_Input/Capture_Tests.cs @@ -70,7 +70,9 @@ private async Task RunTest(string testName, Action act = null) App.WaitForElement(target); act(target); +#if HAS_RENDER_TARGET_BITMAP await TakeScreenshotAsync("Result"); +#endif result.GetDependencyPropertyValue("Text").Should().Be("SUCCESS"); } diff --git a/src/Uno.CrossTargetting.targets b/src/Uno.CrossTargetting.targets index 2214c7f005fb..f4f819b5a52d 100644 --- a/src/Uno.CrossTargetting.targets +++ b/src/Uno.CrossTargetting.targets @@ -54,6 +54,7 @@ uses render transforms instead of native scroll feature (if any) - UNO_HAS_MANAGED_POINTERS: Determines if the pointer events are dispatched by uno instead of the system (if any). - HAS_NATIVE_IMPLICIT_POINTER_CAPTURE: Indicate if the OS once a pointer is pressed, OS will send all pointer event to the same target (a.k.a. OriginalSource) + - HAS_RENDER_TARGET_BITMAP : Determines if the render target bitmap is implemented (and can be used to take screenshot in runtime tests) - Constants for Xamarin backends and SDK versions: https://docs.microsoft.com/en-us/xamarin/cross-platform/app-fundamentals/building-cross-platform-applications/platform-divergence-abstraction-divergent-implementation#conditional-compilation --> @@ -68,7 +69,7 @@ $(DefineConstants);__SKIA__;UNO_HAS_ENHANCED_HIT_TEST_PROPERTY;UNO_HAS_MANAGED_SCROLL_PRESENTER;UNO_HAS_MANAGED_POINTERS;SUPPORTS_RTL;UNO_HAS_ENHANCED_LIFECYCLE;UNO_HAS_BORDER_VISUAL - $(DefineConstants);UNO_SUPPORTS_NATIVEHOST;HAS_INPUT_INJECTOR + $(DefineConstants);UNO_SUPPORTS_NATIVEHOST;HAS_INPUT_INJECTOR;HAS_RENDER_TARGET_BITMAP @@ -80,21 +81,21 @@ - $(DefineConstants);XAMARIN;UNO_HAS_UIELEMENT_IMPLICIT_PINNING;HAS_NATIVE_IMPLICIT_POINTER_CAPTURE + $(DefineConstants);XAMARIN;UNO_HAS_UIELEMENT_IMPLICIT_PINNING;HAS_NATIVE_IMPLICIT_POINTER_CAPTURE;HAS_RENDER_TARGET_BITMAP - $(DefineConstants);XAMARIN;UNO_HAS_UIELEMENT_IMPLICIT_PINNING;HAS_NATIVE_IMPLICIT_POINTER_CAPTURE + $(DefineConstants);XAMARIN;UNO_HAS_UIELEMENT_IMPLICIT_PINNING;HAS_NATIVE_IMPLICIT_POINTER_CAPTURE;HAS_RENDER_TARGET_BITMAP 13.1 - $(DefineConstants);__MACOS__;XAMARIN;UNO_HAS_UIELEMENT_IMPLICIT_PINNING;UNO_HAS_MANAGED_SCROLL_PRESENTER;UNO_HAS_MANAGED_POINTERS + $(DefineConstants);__MACOS__;XAMARIN;UNO_HAS_UIELEMENT_IMPLICIT_PINNING;UNO_HAS_MANAGED_SCROLL_PRESENTER;UNO_HAS_MANAGED_POINTERS;HAS_RENDER_TARGET_BITMAP 10.15 - $(DefineConstants);__ANDROID__;XAMARIN;HAS_NATIVE_IMPLICIT_POINTER_CAPTURE + $(DefineConstants);__ANDROID__;XAMARIN;HAS_NATIVE_IMPLICIT_POINTER_CAPTURE;HAS_RENDER_TARGET_BITMAP 21.0 @@ -108,13 +109,13 @@ - $(DefineConstants);HAS_INPUT_INJECTOR;WINDOWS_WINUI + $(DefineConstants);HAS_INPUT_INJECTOR;WINDOWS_WINUI;HAS_RENDER_TARGET_BITMAP 10.0.19041.0 10.0.19041.0 - $(DefineConstants);NETFX_CORE;HAS_INPUT_INJECTOR + $(DefineConstants);NETFX_CORE;HAS_INPUT_INJECTOR;HAS_RENDER_TARGET_BITMAP diff --git a/src/Uno.UI.RuntimeTests/Helpers/UITestHelper.cs b/src/Uno.UI.RuntimeTests/Helpers/UITestHelper.cs index 996698afc6cf..42ebadf92f8a 100644 --- a/src/Uno.UI.RuntimeTests/Helpers/UITestHelper.cs +++ b/src/Uno.UI.RuntimeTests/Helpers/UITestHelper.cs @@ -67,6 +67,7 @@ public static async Task Load(T element, Func? isLoaded = null /// public static async Task ScreenShot(FrameworkElement element, bool opaque = false, ScreenShotScalingMode scaling = ScreenShotScalingMode.UsePhysicalPixelsWithImplicitScaling) { +#if HAS_RENDER_TARGET_BITMAP var renderer = new RenderTargetBitmap(); element.UpdateLayout(); await TestServices.WindowHelper.WaitForIdle(); @@ -96,6 +97,9 @@ public static async Task ScreenShot(FrameworkElement element, bool op } return bitmap; +#else + throw new NotSupportedException("Cannot take screenshot on this platform."); +#endif } public enum ScreenShotScalingMode diff --git a/src/Uno.UI.RuntimeTests/UITests/_Engine/RuntimeTestsApp.cs b/src/Uno.UI.RuntimeTests/UITests/_Engine/RuntimeTestsApp.cs index e832f1a1bfb7..24c827211272 100644 --- a/src/Uno.UI.RuntimeTests/UITests/_Engine/RuntimeTestsApp.cs +++ b/src/Uno.UI.RuntimeTests/UITests/_Engine/RuntimeTestsApp.cs @@ -60,6 +60,7 @@ public async Task RunAsync(string metadataName) { assemblyName = "UnoIslands" + assemblyName; } + if (Type.GetType($"{metadataName}, {assemblyName}") is { } sampleType && Activator.CreateInstance(sampleType) is FrameworkElement sample) { diff --git a/src/Uno.UI/UI/Xaml/Media/Imaging/RenderTargetBitmap.UnmanagedArrayOfBytes.cs b/src/Uno.UI/UI/Xaml/Media/Imaging/RenderTargetBitmap.UnmanagedArrayOfBytes.cs new file mode 100644 index 000000000000..b6edfc4e1e35 --- /dev/null +++ b/src/Uno.UI/UI/Xaml/Media/Imaging/RenderTargetBitmap.UnmanagedArrayOfBytes.cs @@ -0,0 +1,86 @@ +#if !__ANDROID__ +#nullable enable +using System; +using System.Buffers; +using System.Linq; +using System.Runtime.InteropServices; + +namespace Microsoft.UI.Xaml.Media.Imaging; + +public partial class RenderTargetBitmap +{ + private unsafe class UnmanagedArrayOfBytes + { + public nint Pointer; + public int Length { get; } + + public UnmanagedArrayOfBytes(int length) + { + Length = length; + Pointer = Marshal.AllocHGlobal(length); + GC.AddMemoryPressure(length); + } + + public byte this[int index] + { + get + { + return ((byte*)Pointer.ToPointer())[index]; + } + set + { + ((byte*)Pointer.ToPointer())[index] = value; + } + } + + ~UnmanagedArrayOfBytes() + { + Marshal.FreeHGlobal(Pointer); + GC.RemoveMemoryPressure(Length); + } + } + + // This is to avoid LOH array allocations + // https://stackoverflow.com/questions/52190423/c-sharp-access-unmanaged-array-using-memoryt-or-arraysegmentt + private sealed unsafe class UnmanagedMemoryManager : MemoryManager + where T : unmanaged + { + private readonly T* _pointer; + private readonly int _length; + + /// + /// Create a new UnmanagedMemoryManager instance at the given pointer and size + /// + public UnmanagedMemoryManager(T* pointer, int length) + { + if (length < 0) throw new ArgumentOutOfRangeException(nameof(length)); + _pointer = pointer; + _length = length; + } + /// + /// Obtains a span that represents the region + /// + public override Span GetSpan() => new(_pointer, _length); + + /// + /// Provides access to a pointer that represents the data (note: no actual pin occurs) + /// + public override MemoryHandle Pin(int elementIndex = 0) + { + if (elementIndex < 0 || elementIndex >= _length) + throw new ArgumentOutOfRangeException(nameof(elementIndex)); + return new MemoryHandle(_pointer + elementIndex); + } + + /// + /// Has no effect + /// + public override void Unpin() { } + + /// + /// Releases all resources associated with this object + /// + protected override void Dispose(bool disposing) { } + } +} +#endif diff --git a/src/Uno.UI/UI/Xaml/Media/Imaging/RenderTargetBitmap.cs b/src/Uno.UI/UI/Xaml/Media/Imaging/RenderTargetBitmap.cs index 745016074829..04d776da28af 100644 --- a/src/Uno.UI/UI/Xaml/Media/Imaging/RenderTargetBitmap.cs +++ b/src/Uno.UI/UI/Xaml/Media/Imaging/RenderTargetBitmap.cs @@ -1,7 +1,4 @@ #nullable enable -#if !__IOS__ && !__ANDROID__ && !__SKIA__ && !__MACOS__ -#define NOT_IMPLEMENTED -#endif using System; using System.Collections.Generic; @@ -14,107 +11,39 @@ using Uno.UI.Xaml.Media; using Buffer = Windows.Storage.Streams.Buffer; using System.Buffers; -using System.Runtime.InteropServices; - using WinUICoreServices = Uno.UI.Xaml.Core.CoreServices; namespace Microsoft.UI.Xaml.Media.Imaging { -#if NOT_IMPLEMENTED +#if !HAS_RENDER_TARGET_BITMAP [global::Uno.NotImplemented("IS_UNIT_TESTS", "__WASM__", "__NETSTD_REFERENCE__")] #endif public partial class RenderTargetBitmap : ImageSource { -#if !__ANDROID__ - // This is to avoid LOH array allocations - private unsafe class UnmanagedArrayOfBytes - { - public nint Pointer; - public int Length { get; } - - public UnmanagedArrayOfBytes(int length) - { - Length = length; - Pointer = Marshal.AllocHGlobal(length); - GC.AddMemoryPressure(length); - } - - public byte this[int index] - { - get - { - return ((byte*)Pointer.ToPointer())[index]; - } - set - { - ((byte*)Pointer.ToPointer())[index] = value; - } - } - - ~UnmanagedArrayOfBytes() - { - Marshal.FreeHGlobal(Pointer); - GC.RemoveMemoryPressure(Length); - } - } - - // https://stackoverflow.com/questions/52190423/c-sharp-access-unmanaged-array-using-memoryt-or-arraysegmentt - private sealed unsafe class UnmanagedMemoryManager : MemoryManager - where T : unmanaged - { - private readonly T* _pointer; - private readonly int _length; - - /// - /// Create a new UnmanagedMemoryManager instance at the given pointer and size - /// - public UnmanagedMemoryManager(T* pointer, int length) - { - if (length < 0) throw new ArgumentOutOfRangeException(nameof(length)); - _pointer = pointer; - _length = length; - } - /// - /// Obtains a span that represents the region - /// - public override Span GetSpan() => new Span(_pointer, _length); - - /// - /// Provides access to a pointer that represents the data (note: no actual pin occurs) - /// - public override MemoryHandle Pin(int elementIndex = 0) - { - if (elementIndex < 0 || elementIndex >= _length) - throw new ArgumentOutOfRangeException(nameof(elementIndex)); - return new MemoryHandle(_pointer + elementIndex); - } - - /// - /// Has no effect - /// - public override void Unpin() { } - - /// - /// Releases all resources associated with this object - /// - protected override void Dispose(bool disposing) { } - } -#endif - -#if NOT_IMPLEMENTED +#if !HAS_RENDER_TARGET_BITMAP internal const bool IsImplemented = false; #else internal const bool IsImplemented = true; #endif +#if !HAS_RENDER_TARGET_BITMAP + // The partial API that has to be implemented in each platform + + private static ImageData Open(UnmanagedArrayOfBytes buffer, int bufferLength, int width, int height) + => default; + + private (int ByteCount, int Width, int Height) RenderAsBgra8_Premul(UIElement element, ref UnmanagedArrayOfBytes? buffer, Size? scaledSize = null) + => throw new NotImplementedException("RenderTargetBitmap is not supported on this platform."); +#endif + #region PixelWidth -#if NOT_IMPLEMENTED +#if !HAS_RENDER_TARGET_BITMAP [global::Uno.NotImplemented("IS_UNIT_TESTS", "__WASM__", "__NETSTD_REFERENCE__")] #endif public static DependencyProperty PixelWidthProperty { get; } = DependencyProperty.Register( "PixelWidth", typeof(int), typeof(RenderTargetBitmap), new FrameworkPropertyMetadata(default(int))); -#if NOT_IMPLEMENTED +#if !HAS_RENDER_TARGET_BITMAP [global::Uno.NotImplemented("IS_UNIT_TESTS", "__WASM__", "__NETSTD_REFERENCE__")] #endif public int PixelWidth @@ -126,13 +55,13 @@ public int PixelWidth #region PixelHeight -#if NOT_IMPLEMENTED +#if !HAS_RENDER_TARGET_BITMAP [global::Uno.NotImplemented("IS_UNIT_TESTS", "__WASM__", "__NETSTD_REFERENCE__")] #endif public static DependencyProperty PixelHeightProperty { get; } = DependencyProperty.Register( "PixelHeight", typeof(int), typeof(RenderTargetBitmap), new FrameworkPropertyMetadata(default(int))); -#if NOT_IMPLEMENTED +#if !HAS_RENDER_TARGET_BITMAP [global::Uno.NotImplemented("IS_UNIT_TESTS", "__WASM__", "__NETSTD_REFERENCE__")] #endif public int PixelHeight @@ -142,10 +71,10 @@ public int PixelHeight } #endregion -#if !__ANDROID__ - private UnmanagedArrayOfBytes? _buffer; -#else +#if __ANDROID__ private byte[]? _buffer; +#else + private UnmanagedArrayOfBytes? _buffer; #endif private int _bufferSize; @@ -166,12 +95,7 @@ private protected override bool TryOpenSourceSync(int? targetWidth, int? targetH return image.HasData; } -#if NOT_IMPLEMENTED - private static ImageData Open(UnmanagedArrayOfBytes buffer, int bufferLength, int width, int height) - => default; -#endif - -#if NOT_IMPLEMENTED +#if !HAS_RENDER_TARGET_BITMAP [global::Uno.NotImplemented("IS_UNIT_TESTS", "__WASM__", "__NETSTD_REFERENCE__")] #endif public IAsyncAction RenderAsync(UIElement? element, int scaledWidth, int scaledHeight) @@ -199,7 +123,7 @@ public IAsyncAction RenderAsync(UIElement? element, int scaledWidth, int scaledH return Task.CompletedTask; }); -#if NOT_IMPLEMENTED +#if !HAS_RENDER_TARGET_BITMAP [global::Uno.NotImplemented("IS_UNIT_TESTS", "__WASM__", "__NETSTD_REFERENCE__")] #endif public IAsyncAction RenderAsync(UIElement? element) @@ -227,7 +151,7 @@ public IAsyncAction RenderAsync(UIElement? element) return Task.CompletedTask; }); -#if NOT_IMPLEMENTED +#if !HAS_RENDER_TARGET_BITMAP [global::Uno.NotImplemented("IS_UNIT_TESTS", "__WASM__", "__NETSTD_REFERENCE__")] #endif public IAsyncOperation GetPixelsAsync() @@ -235,27 +159,22 @@ public IAsyncOperation GetPixelsAsync() { if (_buffer is null) { - return Task.FromResult(new Buffer(Array.Empty())); + return Task.FromResult(new Buffer([])); } -#if !__ANDROID__ +#if __ANDROID__ + return Task.FromResult(new Buffer(_buffer.AsMemory().Slice(0, _bufferSize))); +#else unsafe { var mem = new UnmanagedMemoryManager((byte*)_buffer.Pointer.ToPointer(), _bufferSize); return Task.FromResult(new Buffer(mem.Memory.Slice(0, _bufferSize))); } -#else - return Task.FromResult(new Buffer(_buffer.AsMemory().Slice(0, _bufferSize))); #endif }); -#if NOT_IMPLEMENTED - private (int ByteCount, int Width, int Height) RenderAsBgra8_Premul(UIElement element, ref UnmanagedArrayOfBytes? buffer, Size? scaledSize = null) - => throw new NotImplementedException("RenderTargetBitmap is not supported on this platform."); -#endif - #region Misc static helpers -#if !NOT_IMPLEMENTED +#if HAS_RENDER_TARGET_BITMAP #if __ANDROID__ private static void EnsureBuffer(ref byte[]? buffer, int length) {