-
Notifications
You must be signed in to change notification settings - Fork 1.9k
[Windows] Fix performance issue using Shadows #18337
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from 38 commits
2fac694
3670485
2cb1c4c
c298d5a
43d751b
0c0755b
4e819a9
a295f42
2ac51e9
53c95af
f53996b
5845cac
08b6d15
872bb6c
5d7c443
a47b833
b12e282
7a3d90f
8e40516
37bc0a5
c1e73b3
40f3783
ba066df
41b7a9d
acce651
7356397
8b22f84
710365e
9d50db7
f01d721
bc5a06d
66da9cb
1780640
87ce964
2011139
6823f8b
9aa9385
4adf5a8
b03f828
3e5fc68
97cfbb1
98e73ad
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,116 @@ | ||
| using System; | ||
| using System.Collections.Generic; | ||
| using Microsoft.Maui.Controls; | ||
| using Microsoft.Maui.Graphics; | ||
|
|
||
| namespace Maui.Controls.Sample.Pages; | ||
|
|
||
| public class ShadowBenchmark : ContentPage | ||
| { | ||
| event Action timerUpdateEvent; | ||
|
|
||
| public ShadowBenchmark() | ||
| { | ||
| Title = "Shadow Benchmark"; | ||
|
|
||
| double fps = 60; | ||
|
|
||
| var timer = Dispatcher.CreateTimer(); | ||
| timer.Interval = TimeSpan.FromSeconds(1 / fps); | ||
| double time = 0; | ||
| DateTime currentTickDateTime = DateTime.Now; | ||
| double deltaTime = 0; | ||
|
|
||
| timer.Tick += delegate | ||
| { | ||
| deltaTime = (DateTime.Now - currentTickDateTime).TotalSeconds; | ||
| currentTickDateTime = DateTime.Now; | ||
| time += deltaTime; | ||
| timerUpdateEvent?.Invoke(); | ||
| }; | ||
|
|
||
| timer.Start(); | ||
|
|
||
| AbsoluteLayout rootAbs = new AbsoluteLayout(); | ||
| Content = rootAbs; | ||
|
|
||
| Label fpsLabel = new Label | ||
| { | ||
| BackgroundColor = Colors.Black, | ||
| TextColor = Colors.White | ||
| }; | ||
|
|
||
| AbsoluteLayout abs = new AbsoluteLayout(); | ||
| rootAbs.Add(abs); | ||
| rootAbs.Add(fpsLabel); | ||
|
|
||
| VerticalStackLayout vert = new VerticalStackLayout(); | ||
| vert.BackgroundColor = Colors.LightGray; | ||
| abs.Add(vert); | ||
|
|
||
| AbsoluteLayout abs2 = new AbsoluteLayout(); | ||
| vert.Add(abs2); | ||
|
|
||
| int numBorders = 3; | ||
| List<Border> borders = new List<Border>(); | ||
|
|
||
| for (int i = 0; i < numBorders; i++) | ||
| { | ||
| Border border = new Border | ||
| { | ||
| HeightRequest = 200, | ||
| WidthRequest = 500, | ||
| BackgroundColor = Colors.DimGray | ||
| }; | ||
| abs2.Add(border); | ||
| borders.Add(border); | ||
| border.Shadow = new Shadow() { Brush = new SolidColorBrush(Colors.Black), Offset = new Point(5, 5), Radius = 5 }; | ||
| } | ||
|
|
||
| Border borderBot = new Border | ||
| { | ||
| HeightRequest = 200, | ||
| WidthRequest = 50, | ||
| BackgroundColor = Colors.DarkGray | ||
| }; | ||
| vert.Add(borderBot); | ||
|
|
||
| timerUpdateEvent += delegate | ||
| { | ||
| double sinVal = (Math.Sin(time) + 1) * 0.5; | ||
| double height = 20 + sinVal * 800; | ||
| for (int i = 0; i < borders.Count; i++) | ||
| { | ||
| borders[i].HeightRequest = height; | ||
| } | ||
| }; | ||
|
|
||
| SizeChanged += delegate | ||
| { | ||
| if (Width > 0) | ||
| { | ||
| abs.WidthRequest = Width; | ||
| abs.HeightRequest = Height; | ||
| vert.WidthRequest = Width; | ||
|
|
||
| for (int i = 0; i < borders.Count; i++) | ||
| { | ||
| borders[i].WidthRequest = Width; | ||
| borders[i].TranslationX = Width * i; | ||
| } | ||
| } | ||
| }; | ||
|
|
||
| DateTime lastUpdateDateTime = DateTime.Now; | ||
| abs2.SizeChanged += delegate | ||
| { | ||
| if (lastUpdateDateTime != DateTime.Now) | ||
| { | ||
| string fps = "FPS " + 1 / (DateTime.Now - lastUpdateDateTime).TotalSeconds; | ||
| fpsLabel.Text = fps; | ||
|
|
||
| lastUpdateDateTime = DateTime.Now; | ||
| } | ||
| }; | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,25 @@ | ||
| <?xml version="1.0" encoding="utf-8" ?> | ||
| <views:BasePage | ||
| xmlns="http://schemas.microsoft.com/dotnet/2021/maui" | ||
| xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" | ||
| x:Class="Maui.Controls.Sample.Pages.ShadowMaskPage" | ||
| xmlns:views="clr-namespace:Maui.Controls.Sample.Pages.Base" | ||
| Title="Shadow Mask - Shadows respect control shape"> | ||
| <VerticalStackLayout | ||
| Padding="12"> | ||
| <Button | ||
| HorizontalOptions="Center" | ||
| Text="Shadow" | ||
| CornerRadius="60" | ||
| HeightRequest="120" | ||
| WidthRequest="120"> | ||
| <Button.Shadow> | ||
| <Shadow | ||
| Brush="Black" | ||
| Opacity="1" | ||
| Radius="10" | ||
| Offset="0, 0" /> | ||
| </Button.Shadow> | ||
| </Button> | ||
| </VerticalStackLayout> | ||
| </views:BasePage> |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,9 @@ | ||
| namespace Maui.Controls.Sample.Pages; | ||
|
|
||
| public partial class ShadowMaskPage | ||
| { | ||
| public ShadowMaskPage() | ||
| { | ||
| InitializeComponent(); | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,134 @@ | ||
| using Microsoft.Maui.Controls.Shapes; | ||
|
|
||
| namespace Maui.Controls.Sample.Issues; | ||
|
|
||
| [Issue(IssueTracker.Github, 18172, "Shadows not drawing/updating correctly in Windows & cover entire screen", PlatformAffected.UWP)] | ||
| public class Issue18172 : TestContentPage | ||
| { | ||
| protected override void Init() | ||
| { | ||
| Title = "Issue 18172"; | ||
|
|
||
| BackgroundColor = Colors.LightPink; | ||
|
|
||
| ScreenSizeMonitor.Instance.setContentPage(this); | ||
|
|
||
| VerticalStackLayout verticalStackLayout = new VerticalStackLayout(); | ||
| Content = verticalStackLayout; | ||
|
|
||
| AbsoluteLayout absoluteLayout = new AbsoluteLayout(); | ||
| verticalStackLayout.Add(absoluteLayout); | ||
| absoluteLayout.Add(TopObjectWithShadow.Instance); | ||
| } | ||
| } | ||
|
|
||
| public class TopObjectWithShadow : AbsoluteLayout | ||
| { | ||
| public static TopObjectWithShadow Instance { get { return lazy.Value; } } | ||
| static readonly Lazy<TopObjectWithShadow> lazy = new Lazy<TopObjectWithShadow>(() => new TopObjectWithShadow()); | ||
|
|
||
| public TopSubComponent topMenuFrameComponent; | ||
|
|
||
| private TopObjectWithShadow() | ||
| { | ||
| IgnoreSafeArea = true; | ||
|
|
||
| topMenuFrameComponent = new TopSubComponent(); | ||
| Add(topMenuFrameComponent); | ||
|
|
||
| topMenuFrameComponent.Shadow = new Shadow() | ||
| { | ||
| Offset = new Point(0, 5), | ||
| Radius = 5 | ||
| }; | ||
|
|
||
| ScreenSizeMonitor.Instance.ScreenSizeChanged += OnScreenSizeChanged; | ||
| } | ||
|
|
||
| void OnScreenSizeChanged() | ||
| { | ||
| if (topMenuFrameComponent != null) | ||
| { | ||
| WidthRequest = ScreenSizeMonitor.Instance.screenWidth; | ||
| HeightRequest = ScreenSizeMonitor.Instance.screenHeight; | ||
|
|
||
| topMenuFrameComponent.resizeFunction(); | ||
| } | ||
| } | ||
| } | ||
|
|
||
| public class TopSubComponent : AbsoluteLayout | ||
| { | ||
| readonly Border _frameBorder; | ||
| readonly double _frameBaseHeight = 68; | ||
|
|
||
| public TopSubComponent() | ||
| { | ||
| _frameBorder = new Border | ||
| { | ||
| AutomationId = "BorderControl" | ||
| }; | ||
|
|
||
| Add(_frameBorder); | ||
| IgnoreSafeArea = true; | ||
| _frameBorder.HeightRequest = _frameBaseHeight; | ||
| _frameBorder.BackgroundColor = Colors.White; | ||
| _frameBorder.Margin = new Thickness(0, 20, 0, 0); | ||
| _frameBorder.StrokeShape = new RoundRectangle() { CornerRadius = new CornerRadius(30, 30, 30, 30) }; | ||
| _frameBorder.StrokeThickness = 0; | ||
| } | ||
| public void resizeFunction() | ||
| { | ||
| WidthRequest = ScreenSizeMonitor.Instance.screenWidth; | ||
| HeightRequest = ScreenSizeMonitor.Instance.screenHeight; | ||
|
|
||
| _frameBorder.WidthRequest = ScreenSizeMonitor.Instance.screenWidth; | ||
| _frameBorder.HeightRequest = _frameBaseHeight; | ||
| } | ||
| } | ||
|
|
||
| class ScreenSizeMonitor | ||
| { | ||
| public ContentPage pageToMonitor; | ||
|
|
||
| private static readonly Lazy<ScreenSizeMonitor> lazy = new Lazy<ScreenSizeMonitor>(() => new ScreenSizeMonitor()); | ||
| public static ScreenSizeMonitor Instance { get { return lazy.Value; } } | ||
|
|
||
| public double screenWidth = 0; | ||
| public double screenHeight = 0; | ||
| public event Action ScreenSizeChanged = null; | ||
|
|
||
| public void setContentPage(ContentPage contentPageToMonitor) | ||
| { | ||
| pageToMonitor = contentPageToMonitor; | ||
| StartScreenMonitor(); | ||
| } | ||
|
|
||
| void StartScreenMonitor() | ||
| { | ||
| UpdateFunction(); | ||
|
|
||
| pageToMonitor.SizeChanged += delegate | ||
| { | ||
| UpdateFunction(); | ||
| }; | ||
| } | ||
|
|
||
| void UpdateFunction() | ||
| { | ||
| if (pageToMonitor.Width > 0 && pageToMonitor.Height > 0) | ||
| { | ||
| screenWidth = pageToMonitor.Width; | ||
| screenHeight = pageToMonitor.Height; | ||
|
|
||
| invokeScreenSizeChanged(); | ||
| } | ||
| } | ||
| public void invokeScreenSizeChanged() | ||
| { | ||
| MainThread.BeginInvokeOnMainThread(() => | ||
| { | ||
| ScreenSizeChanged?.Invoke(); | ||
| }); | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,25 @@ | ||
| #if WINDOWS // Issue only happens on Windows | ||
| using NUnit.Framework; | ||
| using UITest.Appium; | ||
| using UITest.Core; | ||
|
|
||
| namespace Microsoft.Maui.TestCases.Tests.Issues; | ||
|
|
||
| class Issue18172 : _IssuesUITest | ||
| { | ||
| public Issue18172(TestDevice device) | ||
| : base(device) | ||
| { } | ||
|
|
||
| public override string Issue => "Shadows not drawing/updating correctly in Windows & cover entire screen"; | ||
|
|
||
| [Test] | ||
| [Category(UITestCategories.GraphicsView)] | ||
| public async Task Issue18172Test() | ||
| { | ||
| await Task.Delay(500); | ||
|
|
||
| VerifyScreenshot(); | ||
| } | ||
| } | ||
| #endif | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @jsuarezruiz I know this was wrong even before, but there should be no shadows here given that the code does |
| Original file line number | Diff line number | Diff line change | ||||||
|---|---|---|---|---|---|---|---|---|
|
|
@@ -24,28 +24,32 @@ public static async Task<CompositionBrush> GetAlphaMaskAsync(this UIElement elem | |||||||
|
|
||||||||
| try | ||||||||
| { | ||||||||
| //For some reason, using TextBlock and getting the AlphaMask | ||||||||
| //generates a shadow with a size more smaller than the control size. | ||||||||
| if (element is TextBlock textElement) | ||||||||
| var visual = ElementCompositionPreview.GetElementVisual(element); | ||||||||
jsuarezruiz marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||
| var isClipped = visual.Clip is not null; | ||||||||
|
|
||||||||
| if (!isClipped && element is TextBlock textElement) | ||||||||
| { | ||||||||
| return textElement.GetAlphaMask(); | ||||||||
| } | ||||||||
| if (element is Image image) | ||||||||
| if (!isClipped && element is Image image) | ||||||||
| { | ||||||||
| return image.GetAlphaMask(); | ||||||||
| } | ||||||||
| if (element is Shape shape) | ||||||||
| if (!isClipped && element is Shape shape) | ||||||||
| { | ||||||||
| return shape.GetAlphaMask(); | ||||||||
| } | ||||||||
| else if (element is FrameworkElement frameworkElement) | ||||||||
| if (!isClipped && element is ContentPanel contentPanel) | ||||||||
mattleibow marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||
| { | ||||||||
| return contentPanel.BorderPath?.GetAlphaMask(); | ||||||||
|
||||||||
| return contentPanel.BorderPath?.GetAlphaMask(); | |
| mask = contentPanel.BorderPath?.GetAlphaMask(); | |
| break; |
Uh oh!
There was an error while loading. Please reload this page.