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

Optimize working with EllipseGeometry/RectangleGeometry, reduce allocs #9981

Open
wants to merge 13 commits into
base: main
Choose a base branch
from
Open
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
Expand Up @@ -2,25 +2,10 @@
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

//

using System;
using MS.Internal;
using System.ComponentModel;
using System.ComponentModel.Design.Serialization;
using System.Reflection;
using System.Collections;
using System.Text;
using System.Globalization;
using System.Windows.Media;
using System.Windows.Media.Composition;
using System.Windows;
using System.Text.RegularExpressions;
using System.Windows.Media.Animation;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Security;
using SR=MS.Internal.PresentationCore.SR;

using SR = MS.Internal.PresentationCore.SR;

namespace System.Windows.Media
{
Expand All @@ -34,9 +19,7 @@ public sealed partial class EllipseGeometry : Geometry
/// <summary>
///
/// </summary>
public EllipseGeometry()
{
}
public EllipseGeometry() { }

/// <summary>
/// Constructor - sets the ellipse to the paramters with the given transformation
Expand All @@ -45,7 +28,7 @@ public EllipseGeometry(Rect rect)
{
if (rect.IsEmpty)
{
throw new System.ArgumentException(SR.Format(SR.Rect_Empty, "rect"));
throw new ArgumentException(SR.Format(SR.Rect_Empty, nameof(rect)));
}

RadiusX = (rect.Right - rect.X) * (1.0 / 2.0);
Expand All @@ -56,10 +39,7 @@ public EllipseGeometry(Rect rect)
/// <summary>
/// Constructor - sets the ellipse to the parameters
/// </summary>
public EllipseGeometry(
Point center,
double radiusX,
double radiusY)
public EllipseGeometry(Point center, double radiusX, double radiusY)
{
Center = center;
RadiusX = radiusX;
Expand All @@ -69,11 +49,7 @@ public EllipseGeometry(
/// <summary>
/// Constructor - sets the ellipse to the parameters
/// </summary>
public EllipseGeometry(
Point center,
double radiusX,
double radiusY,
Transform transform) : this(center, radiusX, radiusY)
h3xds1nz marked this conversation as resolved.
Show resolved Hide resolved
public EllipseGeometry(Point center, double radiusX, double radiusY, Transform transform) : this(center, radiusX, radiusY)
{
Transform = transform;
}
Expand All @@ -96,14 +72,13 @@ public override Rect Bounds
if (transform == null || transform.IsIdentity)
{
Point currentCenter = Center;
Double currentRadiusX = RadiusX;
Double currentRadiusY = RadiusY;

boundsRect = new Rect(
currentCenter.X - Math.Abs(currentRadiusX),
currentCenter.Y - Math.Abs(currentRadiusY),
2.0 * Math.Abs(currentRadiusX),
2.0 * Math.Abs(currentRadiusY));
double currentRadiusX = RadiusX;
double currentRadiusY = RadiusY;

boundsRect = new Rect(currentCenter.X - Math.Abs(currentRadiusX),
currentCenter.Y - Math.Abs(currentRadiusY),
2.0 * Math.Abs(currentRadiusX),
2.0 * Math.Abs(currentRadiusY));
}
else
{
Expand All @@ -114,9 +89,7 @@ public override Rect Bounds
// it's easier to let unmanaged code do the work for us.
//

Matrix geometryMatrix;

Transform.GetTransformValue(transform, out geometryMatrix);
Transform.GetTransformValue(transform, out Matrix geometryMatrix);

boundsRect = EllipseGeometry.GetBoundsHelper(
null /* no pen */,
Expand All @@ -139,87 +112,46 @@ public override Rect Bounds
/// </summary>
internal override Rect GetBoundsInternal(Pen pen, Matrix matrix, double tolerance, ToleranceType type)
{
Matrix geometryMatrix;

Transform.GetTransformValue(Transform, out geometryMatrix);

return EllipseGeometry.GetBoundsHelper(
pen,
matrix,
Center,
RadiusX,
RadiusY,
geometryMatrix,
tolerance,
type);
Transform.GetTransformValue(Transform, out Matrix geometryMatrix);

return EllipseGeometry.GetBoundsHelper(pen, matrix, Center, RadiusX, RadiusY, geometryMatrix, tolerance, type);
}

internal static Rect GetBoundsHelper(Pen pen, Matrix worldMatrix, Point center, double radiusX, double radiusY,
Matrix geometryMatrix, double tolerance, ToleranceType type)
internal static unsafe Rect GetBoundsHelper(Pen pen, Matrix worldMatrix, Point center, double radiusX, double radiusY,
Matrix geometryMatrix, double tolerance, ToleranceType type)
{
Rect rect;

if ( (pen == null || pen.DoesNotContainGaps) &&
worldMatrix.IsIdentity && geometryMatrix.IsIdentity)
if ((pen is null || pen.DoesNotContainGaps) && worldMatrix.IsIdentity && geometryMatrix.IsIdentity)
{
double strokeThickness = 0.0;

if (Pen.ContributesToBounds(pen))
{
strokeThickness = Math.Abs(pen.Thickness);
}
double strokeThickness = Pen.ContributesToBounds(pen) ? Math.Abs(pen.Thickness) : 0.0;

rect = new Rect(
center.X - Math.Abs(radiusX)-0.5*strokeThickness,
center.Y - Math.Abs(radiusY)-0.5*strokeThickness,
2.0 * Math.Abs(radiusX)+strokeThickness,
2.0 * Math.Abs(radiusY)+strokeThickness);
return new Rect(center.X - Math.Abs(radiusX) - 0.5 * strokeThickness,
center.Y - Math.Abs(radiusY) - 0.5 * strokeThickness,
2.0 * Math.Abs(radiusX) + strokeThickness,
2.0 * Math.Abs(radiusY) + strokeThickness);
}
else
{
unsafe
Point* ptrPoints = stackalloc Point[(int)PointCount];
EllipseGeometry.InitializePointList(ptrPoints, (int)PointCount, center, radiusX, radiusY);

fixed (byte* ptrTypes = RoundedPathTypes) // Merely retrieves the pointer to static PE data, no actual pinning occurs
{
Point* pPoints = stackalloc Point[(int)c_pointCount];
EllipseGeometry.GetPointList(pPoints, c_pointCount, center, radiusX, radiusY);

fixed (byte* pTypes = RoundedPathTypes) //Merely retrieves the pointer to static PE data, no actual pinning occurs
{
rect = Geometry.GetBoundsHelper(
pen,
&worldMatrix,
pPoints,
pTypes,
c_pointCount,
c_segmentCount,
&geometryMatrix,
tolerance,
type,
false); // skip hollows - meaningless here, this is never a hollow
}
return Geometry.GetBoundsHelper(pen, &worldMatrix, ptrPoints, ptrTypes, PointCount, SegmentCount, &geometryMatrix,
tolerance, type, false); // skip hollows - meaningless here, this is never a hollow
}
}

return rect;
}

internal override bool ContainsInternal(Pen pen, Point hitPoint, double tolerance, ToleranceType type)
{
unsafe
{
Point* pPoints = stackalloc Point[(int)GetPointCount()];
EllipseGeometry.GetPointList(pPoints, GetPointCount(), Center, RadiusX, RadiusY);
Point* ptrPoints = stackalloc Point[(int)PointCount];
EllipseGeometry.InitializePointList(ptrPoints, (int)PointCount, Center, RadiusX, RadiusY);

fixed (byte* pTypes = RoundedPathTypes) //Merely retrieves the pointer to static PE data, no actual pinning occurs
fixed (byte* ptrTypes = RoundedPathTypes) // Merely retrieves the pointer to static PE data, no actual pinning occurs
{
return ContainsInternal(
pen,
hitPoint,
tolerance,
type,
pPoints,
GetPointCount(),
pTypes,
GetSegmentCount());
return ContainsInternal(pen, hitPoint, tolerance, type, ptrPoints, PointCount, ptrTypes, SegmentCount);
}
}
}
Expand Down Expand Up @@ -267,40 +199,33 @@ public override double GetArea(double tolerance, ToleranceType type)

internal override PathFigureCollection GetTransformedFigureCollection(Transform transform)
{
Point [] points = GetPointList();
// Initialize the point list
Span<Point> points = stackalloc Point[(int)PointCount];
InitializePointList(points);

// Get the combined transform argument with the internal transform
Matrix matrix = GetCombinedMatrix(transform);
if (!matrix.IsIdentity)
{
for (int i=0; i<points.Length; i++)
for (int i = 0; i < points.Length; i++)
{
points[i] *= matrix;
}
}

PathFigureCollection figureCollection = new PathFigureCollection();
figureCollection.Add(
new PathFigure(
points[0],
new PathSegment[]{
new BezierSegment(points[1], points[2], points[3], true, true),
new BezierSegment(points[4], points[5], points[6], true, true),
new BezierSegment(points[7], points[8], points[9], true, true),
new BezierSegment(points[10], points[11], points[12], true, true)},
true
)
);

return figureCollection;
return new PathFigureCollection(1) { new PathFigure(points[0], [new BezierSegment(points[1], points[2], points[3], true, true),
new BezierSegment(points[4], points[5], points[6], true, true),
new BezierSegment(points[7], points[8], points[9], true, true),
new BezierSegment(points[10], points[11], points[12], true, true)],
closed: true) };
}

/// <summary>
/// GetAsPathGeometry - return a PathGeometry version of this Geometry
/// </summary>
internal override PathGeometry GetAsPathGeometry()
{
PathStreamGeometryContext ctx = new PathStreamGeometryContext(FillRule.EvenOdd, Transform);
PathStreamGeometryContext ctx = new(FillRule.EvenOdd, Transform);
PathGeometry.ParsePathGeometryData(GetPathGeometryData(), ctx);

return ctx.GetPathGeometry();
Expand All @@ -317,14 +242,13 @@ internal override PathGeometryData GetPathGeometryData()
return Geometry.GetEmptyPathGeometryData();
}

PathGeometryData data = new PathGeometryData();
data.FillRule = FillRule.EvenOdd;
data.Matrix = CompositionResourceManager.TransformToMilMatrix3x2D(Transform);

Point[] points = GetPointList();
PathGeometryData data = new() { FillRule = FillRule.EvenOdd, Matrix = CompositionResourceManager.TransformToMilMatrix3x2D(Transform) };

ByteStreamGeometryContext ctx = new ByteStreamGeometryContext();
// Initialize the point list
Span<Point> points = stackalloc Point[(int)PointCount];
InitializePointList(points);

ByteStreamGeometryContext ctx = new();
ctx.BeginFigure(points[0], true /* is filled */, true /* is closed */);

// i == 0, 3, 6, 9
Expand All @@ -340,32 +264,28 @@ internal override PathGeometryData GetPathGeometryData()
}

/// <summary>
/// Initializes the point list into <paramref name="destination"/>. Optionally pins the source if not stack-allocated.
/// </summary>
/// <returns></returns>
private Point[] GetPointList()
private unsafe void InitializePointList(Span<Point> destination)
{
Point[] points = new Point[GetPointCount()];

unsafe
fixed (Point* ptrPoints = destination) // In case this is stackallocated, it's a no-op
{
fixed(Point *pPoints = points)
{
EllipseGeometry.GetPointList(pPoints, GetPointCount(), Center, RadiusX, RadiusY);
}
EllipseGeometry.InitializePointList(ptrPoints, destination.Length, Center, RadiusX, RadiusY);
}

return points;
}

private unsafe static void GetPointList(Point * points, uint pointsCount, Point center, double radiusX, double radiusY)
/// <summary>
/// Initializes the point list specified by <paramref name="points"/>. The pointer must be pinned.
/// </summary>
private unsafe static void InitializePointList(Point* points, int pointsCount, Point center, double radiusX, double radiusY)
{
Invariant.Assert(pointsCount >= c_pointCount);
Invariant.Assert((uint)pointsCount >= PointCount);

radiusX = Math.Abs(radiusX);
radiusY = Math.Abs(radiusY);

// Set the X coordinates
double mid = radiusX * c_arcAsBezier;
double mid = radiusX * ArcAsBezier;

points[0].X = points[1].X = points[11].X = points[12].X = center.X + radiusX;
points[2].X = points[10].X = center.X + mid;
Expand All @@ -374,37 +294,34 @@ private unsafe static void GetPointList(Point * points, uint pointsCount, Point
points[5].X = points[6].X = points[7].X = center.X - radiusX;

// Set the Y coordinates
mid = radiusY * c_arcAsBezier;
mid = radiusY * ArcAsBezier;

points[2].Y = points[3].Y = points[4].Y = center.Y + radiusY;
points[1].Y = points[5].Y = center.Y + mid;
points[0].Y = points[6].Y = points[12].Y = center.Y;
points[7].Y = points[11].Y = center.Y - mid;
points[8].Y = points[9].Y = points[10].Y = center.Y - radiusY;
}

private static uint GetPointCount() { return c_pointCount; }
private static uint GetSegmentCount() { return c_segmentCount; }

#region Static Data

// Approximating a 1/4 circle with a Bezier curve _
internal const double c_arcAsBezier = 0.5522847498307933984; // =( \/2 - 1)*4/3
internal const double ArcAsBezier = 0.5522847498307933984; // =( \/2 - 1)*4/3

private const UInt32 c_segmentCount = 4;
private const UInt32 c_pointCount = 13;
private const uint SegmentCount = 4;
private const uint PointCount = 13;

private const byte c_smoothBezier = (byte)MILCoreSegFlags.SegTypeBezier |
(byte)MILCoreSegFlags.SegIsCurved |
(byte)MILCoreSegFlags.SegSmoothJoin;
private const byte SmoothBezier = (byte)MILCoreSegFlags.SegTypeBezier |
(byte)MILCoreSegFlags.SegIsCurved |
(byte)MILCoreSegFlags.SegSmoothJoin;

private static ReadOnlySpan<byte> RoundedPathTypes => [(byte)MILCoreSegFlags.SegTypeBezier |
(byte)MILCoreSegFlags.SegIsCurved |
(byte)MILCoreSegFlags.SegSmoothJoin |
(byte)MILCoreSegFlags.SegClosed,
c_smoothBezier,
c_smoothBezier,
c_smoothBezier];
SmoothBezier,
SmoothBezier,
SmoothBezier];

#endregion
}
Expand Down
Loading