Skip to content
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

refactor(shape): wasm shape perf round-2 #19100

Merged
merged 1 commit into from
Jan 7, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
#if __WASM__

using System;
using Microsoft.UI.Xaml.Media;
using Uno.Xaml;

namespace Uno.UI.RuntimeTests.Tests.Windows_UI_Xaml_Media;

[TestClass]
[RunsOnUIThread]
public class Given_GeometryData
{
[DataTestMethod]
[DataRow("", FillRule.EvenOdd, "")]
[DataRow("F0", FillRule.EvenOdd, "")]
[DataRow("F1", FillRule.Nonzero, "")]
[DataRow(" F1", FillRule.Nonzero, "")]
[DataRow(" F 1", FillRule.Nonzero, "")]
[DataRow("F1 M0 0", FillRule.Nonzero, " M0 0")]
[DataRow(" F1 M0 0", FillRule.Nonzero, " M0 0")]
[DataRow(" F 1 M0 0", FillRule.Nonzero, " M0 0")]
public void When_GeometryData_ParseData_Valid(string rawdata, FillRule rule, string data)
{
var result = GeometryData.ParseData(rawdata);

Assert.AreEqual(rule, result.FillRule);
Assert.AreEqual(data, result.Data);
}

[DataTestMethod]
[DataRow("F")]
[DataRow("F2")]
[DataRow("FF")]
[DataRow("F 2")]
[DataRow("F M0 0")]
public void When_GeometryData_ParseData_Invalid(string rawdata)
{
Assert.ThrowsException<XamlParseException>(() =>
GeometryData.ParseData(rawdata)
);
}
}
#endif
77 changes: 64 additions & 13 deletions src/Uno.UI/UI/Xaml/Media/GeometryData.wasm.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
using System;
using Microsoft.UI.Xaml.Wasm;
using Uno.UI.Xaml;
using Uno.Xaml;

namespace Microsoft.UI.Xaml.Media
{
Expand All @@ -14,38 +16,87 @@
//
// <Path Data="M0,0 L10,10 20,10 20,20, 10,20 Z" />

private const FillRule DefaultFillRule = FillRule.EvenOdd;

private readonly SvgElement _svgElement = new SvgElement("path");

public string Data { get; }

public FillRule FillRule { get; } = FillRule.EvenOdd;
public FillRule FillRule { get; } = DefaultFillRule;

public GeometryData()
{
}

public GeometryData(string data)
{
if ((data.StartsWith('F') || data.StartsWith('f')) && data.Length > 2)
(FillRule, Data) = ParseData(data);

WindowManagerInterop.SetSvgPathAttributes(_svgElement.HtmlId, FillRule == FillRule.Nonzero, Data);
}

internal static (FillRule FillRule, string Data) ParseData(string data)
{
if (data == "F")
{
// TODO: support spaces between the F and the 0/1
// uncompleted fill-rule block: missing value (just 'F' without 0/1 after)
throw new XamlParseException($"Failed to create a 'Data' from the text '{data}'.");
}

FillRule = data[1] == '1' ? FillRule.Nonzero : FillRule.EvenOdd;
Data = data.Substring(2);
if (data.Length >= 2 && TryExtractFillRule(data) is { } result)
{
return (result.Value, data[result.CurrentPosition..]);
}
else
{
Data = data;
return (DefaultFillRule, data);
}
}
private static (FillRule Value, int CurrentPosition)? TryExtractFillRule(string data)
{
// XamlParseException: 'Failed to create a 'Data' from the text 'F2'.' Line number '1' and line position '7'.
// "F1" just fill-rule without data is okay

_svgElement.SetAttribute("d", Data);
var rule = FillRule switch
// syntax: [fillRule] moveCommand drawCommand [drawCommand*] [closeCommand]
// Fill rule:
// There are two possible values for the optional fill rule: F0 or F1. (The F is always uppercase.)
// F0 is the default value; it produces EvenOdd fill behavior, so you don't typically specify it.
// Use F1 to get the Nonzero fill behavior. These fill values align with the values of the FillRule enumeration.
// -- https://learn.microsoft.com/en-us/windows/uwp/xaml-platform/move-draw-commands-syntax#the-basic-syntax

// remark: despite explicitly stated: "The F is always uppercase", WinAppSDK is happily to accept lowercase 'f'.
// remark: you can use any number of whitespaces before/inbetween/after fill-rule/commands/command-parameters.

var inFillRule = false;
for (int i = 0; i < data.Length; i++)
{
var c = data[i];

if (char.IsWhiteSpace(c)) continue;

Check failure on line 75 in src/Uno.UI/UI/Xaml/Media/GeometryData.wasm.cs

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

src/Uno.UI/UI/Xaml/Media/GeometryData.wasm.cs#L75

Add curly braces around the nested statement(s) in this 'if' block.
if (inFillRule)
{
if (c is '1') return (FillRule.Nonzero, i + 1);

Check failure on line 78 in src/Uno.UI/UI/Xaml/Media/GeometryData.wasm.cs

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

src/Uno.UI/UI/Xaml/Media/GeometryData.wasm.cs#L78

Add curly braces around the nested statement(s) in this 'if' block.
if (c is '0') // legacy uno behavior would be to use an `else` instead here

Check failure on line 79 in src/Uno.UI/UI/Xaml/Media/GeometryData.wasm.cs

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

src/Uno.UI/UI/Xaml/Media/GeometryData.wasm.cs#L79

Add curly braces around the nested statement(s) in this 'if' block.
return (FillRule.EvenOdd, i + 1);

throw new XamlParseException($"Failed to create a 'Data' from the text '{data}'.");
}
else if (c is 'F' or 'f')
{
inFillRule = true;
}
else
{
return null;
}
}

if (inFillRule)
{
FillRule.EvenOdd => "evenodd",
FillRule.Nonzero => "nonzero",
_ => "evenodd"
};
_svgElement.SetAttribute("fill-rule", rule);
// uncompleted fill-rule block: missing value (just 'F' without 0/1 after)
throw new XamlParseException($"Failed to create a 'Data' from the text '{data}'.");
}
return null;
}

internal override SvgElement GetSvgElement() => _svgElement;
Expand Down
9 changes: 2 additions & 7 deletions src/Uno.UI/UI/Xaml/Media/GeometryGroup.wasm.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
using Uno.UI.DataBinding;
using Windows.Foundation.Collections;
using Microsoft.UI.Xaml.Wasm;
using Uno.UI.Xaml;

namespace Microsoft.UI.Xaml.Media
{
Expand All @@ -28,13 +29,7 @@ private void OnPropertyChanged(ManagedWeakReference? instance, DependencyPropert

if (property == FillRuleProperty)
{
var rule = FillRule switch
{
FillRule.EvenOdd => "evenodd",
FillRule.Nonzero => "nonzero",
_ => "evenodd"
};
_svgElement.SetAttribute("fill-rule", rule);
WindowManagerInterop.SetSvgFillRule(_svgElement.HtmlId, FillRule == FillRule.Nonzero);
}
else if (property == ChildrenProperty)
{
Expand Down
20 changes: 12 additions & 8 deletions src/Uno.UI/UI/Xaml/Media/PointCollection.wasm.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,17 +11,21 @@ namespace Microsoft.UI.Xaml.Media
{
public partial class PointCollection : IEnumerable<Point>, IList<Point>
{
internal string ToCssString()
internal double[] Flatten()
{
var sb = new StringBuilder();
foreach (var p in _points)
if (_points.Count == 0)
{
sb.Append(p.X.ToStringInvariant());
sb.Append(',');
sb.Append(p.Y.ToStringInvariant());
sb.Append(' '); // We will have an extra space at the end ... which is going to be ignored by browsers!
return Array.Empty<double>();
}
return sb.ToString();

var buffer = new double[_points.Count * 2];
for (int i = 0; i < _points.Count; i++)
{
buffer[i * 2] = _points[i].X;
buffer[i * 2 + 1] = _points[i].Y;
}

return buffer;
}
}
}
8 changes: 2 additions & 6 deletions src/Uno.UI/UI/Xaml/Shapes/Ellipse.wasm.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
using Uno.Extensions;
using Windows.Foundation;
using Microsoft.UI.Xaml.Wasm;
using Uno.UI.Xaml;

namespace Microsoft.UI.Xaml.Shapes
{
Expand All @@ -18,14 +19,9 @@ protected override Size ArrangeOverride(Size finalSize)

var cx = shapeSize.Width / 2;
var cy = shapeSize.Height / 2;

var halfStrokeThickness = ActualStrokeThickness / 2;

_mainSvgElement.SetAttribute(
("cx", cx.ToStringInvariant()),
("cy", cy.ToStringInvariant()),
("rx", (cx - halfStrokeThickness).ToStringInvariant()),
("ry", (cy - halfStrokeThickness).ToStringInvariant()));
WindowManagerInterop.SetSvgEllipseAttributes(_mainSvgElement.HtmlId, cx, cy, cx - halfStrokeThickness, cy - halfStrokeThickness);

return finalSize;
}
Expand Down
9 changes: 3 additions & 6 deletions src/Uno.UI/UI/Xaml/Shapes/Line.wasm.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
using Uno.Extensions;
using Windows.Foundation;
using Microsoft.UI.Xaml.Wasm;
using Uno.UI.Xaml;

namespace Microsoft.UI.Xaml.Shapes
{
Expand All @@ -13,12 +14,8 @@ public Line() : base("line")

protected override Size MeasureOverride(Size availableSize)
{
_mainSvgElement.SetAttribute(
("x1", X1.ToStringInvariant()),
("x2", X2.ToStringInvariant()),
("y1", Y1.ToStringInvariant()),
("y2", Y2.ToStringInvariant())
);
WindowManagerInterop.SetSvgLineAttributes(_mainSvgElement.HtmlId, X1, X2, Y1, Y2);

return MeasureAbsoluteShape(availableSize, this);
}

Expand Down
13 changes: 3 additions & 10 deletions src/Uno.UI/UI/Xaml/Shapes/Polygon.wasm.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,22 +2,15 @@
using Windows.Foundation;
using Microsoft.UI.Xaml.Wasm;
using Uno.Extensions;
using Uno.UI.Xaml;

namespace Microsoft.UI.Xaml.Shapes
{
partial class Polygon
{
protected override Size MeasureOverride(Size availableSize)
{
var points = Points;
if (points == null)
{
_mainSvgElement.RemoveAttribute("points");
}
else
{
_mainSvgElement.SetAttribute("points", points.ToCssString());
}
WindowManagerInterop.SetSvgPolyPoints(_mainSvgElement.HtmlId, Points?.Flatten());

return MeasureAbsoluteShape(availableSize, this);
}
Expand All @@ -42,7 +35,7 @@ internal override void OnPropertyChanged2(DependencyPropertyChangedEventArgs arg

private protected override string GetBBoxCacheKeyImpl() =>
Points is { } points
? ("polygone:" + points.ToCssString())
? ("polygone:" + string.Join(',', points.Flatten()))
: null;
}
}
13 changes: 3 additions & 10 deletions src/Uno.UI/UI/Xaml/Shapes/Polyline.wasm.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,22 +6,15 @@
using Uno.Extensions;
using Microsoft.UI.Xaml.Media;
using System.Collections.Generic;
using Uno.UI.Xaml;

namespace Microsoft.UI.Xaml.Shapes
{
partial class Polyline
{
protected override Size MeasureOverride(Size availableSize)
{
var points = Points;
if (points == null)
{
_mainSvgElement.RemoveAttribute("points");
}
else
{
_mainSvgElement.SetAttribute("points", points.ToCssString());
}
WindowManagerInterop.SetSvgPolyPoints(_mainSvgElement.HtmlId, Points?.Flatten());

return MeasureAbsoluteShape(availableSize, this);
}
Expand All @@ -46,7 +39,7 @@ internal override void OnPropertyChanged2(DependencyPropertyChangedEventArgs arg

private protected override string GetBBoxCacheKeyImpl() =>
Points is { } points
? ("polygone:" + points.ToCssString())
? ("polyline:" + string.Join(',', points.Flatten()))
: null;
}
}
16 changes: 10 additions & 6 deletions src/Uno.UI/UI/Xaml/Shapes/Rectangle.wasm.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
using Windows.Foundation;
using Microsoft.UI.Xaml.Media;
using Microsoft.UI.Xaml.Wasm;
using Uno.UI.Xaml;

namespace Microsoft.UI.Xaml.Shapes
{
Expand All @@ -16,15 +17,18 @@ public Rectangle() : base("rect")
protected override Size ArrangeOverride(Size finalSize)
{
UpdateRender();
_mainSvgElement.SetAttribute(
("rx", RadiusX.ToStringInvariant()),
("ry", RadiusY.ToStringInvariant())
);
var (shapeSize, renderingArea) = ArrangeRelativeShape(finalSize);

Uno.UI.Xaml.WindowManagerInterop.SetSvgElementRect(_mainSvgElement.HtmlId, renderingArea);
WindowManagerInterop.SetSvgRectangleAttributes(
_mainSvgElement.HtmlId,
renderingArea.X, renderingArea.Y, renderingArea.Width, renderingArea.Height,
RadiusX, RadiusY
);

_mainSvgElement.Clip = new RectangleGeometry() { Rect = new Rect(0, 0, finalSize.Width, finalSize.Height) };
_mainSvgElement.Clip = new RectangleGeometry()
{
Rect = new Rect(0, 0, finalSize.Width, finalSize.Height)
};

return finalSize;
}
Expand Down
Loading
Loading