Skip to content

Commit

Permalink
Merge pull request #18476 from ramezgerges/composition_path_clipping_…
Browse files Browse the repository at this point in the history
…linux

perf: reduce SKPaint and SKPath allocations
  • Loading branch information
ramezgerges authored Oct 21, 2024
2 parents dad1f30 + 1def759 commit b6333bd
Show file tree
Hide file tree
Showing 13 changed files with 162 additions and 95 deletions.
9 changes: 8 additions & 1 deletion src/SamplesApp/UITests.Shared/UITests.Shared.projitems
Original file line number Diff line number Diff line change
Expand Up @@ -5350,6 +5350,10 @@
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
</Page>
<Page Include="$(MSBuildThisFileDirectory)Windows_UI_Xaml_Shapes\Path_Clipping.xaml">
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
</Page>
<Page Include="$(MSBuildThisFileDirectory)Windows_UI_Xaml_Shapes\Path_Geometries.xaml">
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
Expand Down Expand Up @@ -8797,6 +8801,9 @@
<Compile Include="$(MSBuildThisFileDirectory)Windows_UI_Xaml_Shapes\Path_Custom.xaml.cs">
<DependentUpon>Path_Custom.xaml</DependentUpon>
</Compile>
<Compile Include="$(MSBuildThisFileDirectory)Windows_UI_Xaml_Shapes\Path_Clipping.xaml.cs">
<DependentUpon>Path_Clipping.xaml</DependentUpon>
</Compile>
<Compile Include="$(MSBuildThisFileDirectory)Windows_UI_Xaml_Shapes\Path_Geometries.xaml.cs">
<DependentUpon>Path_Geometries.xaml</DependentUpon>
</Compile>
Expand Down Expand Up @@ -9841,4 +9848,4 @@
</Compile>
</ItemGroup>
<Import Project="ItemExclusions.props" />
</Project>
</Project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<Page
x:Class="UITests.Windows_UI_Xaml_Shapes.Path_Clipping"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="using:UITests.Windows_UI_Xaml_Shapes"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
Background="{ThemeResource ApplicationPageBackgroundThemeBrush}"
mc:Ignorable="d">

<StackPanel>
<TextBlock Text="The two icons below should not be the same (the first one should be clipped)." />
<Border Height="16" HorizontalAlignment="Stretch" />
<ScrollViewer Height="30" x:Name="sv">
<Path Fill="Red" Data="m 0,0 c -0.639,0 -1.276,0.243 -1.765,0.729 -0.977,0.974 -0.98,2.557 -0.006,3.535 L 16.925,23.032 -1.768,41.725 c -0.976,0.976 -0.976,2.559 0,3.535 0.977,0.976 2.559,0.976 3.536,0 L 22.225,24.803 c 0.975,-0.975 0.976,-2.555 0.004,-3.532 L 1.771,0.736 C 1.283,0.245 0.642,0 0,0" />
</ScrollViewer>
<TextBlock Text="------------" />
<Path Fill="Red" Data="m 0,0 c -0.639,0 -1.276,0.243 -1.765,0.729 -0.977,0.974 -0.98,2.557 -0.006,3.535 L 16.925,23.032 -1.768,41.725 c -0.976,0.976 -0.976,2.559 0,3.535 0.977,0.976 2.559,0.976 3.536,0 L 22.225,24.803 c 0.975,-0.975 0.976,-2.555 0.004,-3.532 L 1.771,0.736 C 1.283,0.245 0.642,0 0,0" />
</StackPanel>
</Page>
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
using Uno.UI.Samples.Controls;
using Microsoft.UI.Xaml.Controls;

namespace UITests.Windows_UI_Xaml_Shapes
{
[Sample("Shapes", IsManualTest = true)]
public sealed partial class Path_Clipping : Page
{
public Path_Clipping()
{
this.InitializeComponent();
Loaded += (_, _) => sv.ScrollToVerticalOffset(9999);
}
}
}
11 changes: 6 additions & 5 deletions src/Uno.UI.Composition/Composition/CompositionMaskBrush.skia.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ public partial class CompositionMaskBrush : CompositionBrush, IOnlineBrush
{
private SKPaint? _sourcePaint;
private SKPaint? _maskPaint;
private SKPaint? _resultPaint;

bool IOnlineBrush.IsOnline
{
Expand All @@ -36,10 +35,13 @@ internal override void UpdatePaint(SKPaint paint, SKRect bounds)

void IOnlineBrush.Paint(in Visual.PaintingSession session, SKRect bounds)
{
_resultPaint ??= new SKPaint() { IsAntialias = true };
using (SkiaHelper.GetTempSKPaint(out var resultPaint))
{
resultPaint.IsAntialias = true;

UpdatePaint(_resultPaint, bounds);
session.Canvas?.DrawRect(bounds, _resultPaint);
UpdatePaint(resultPaint, bounds);
session.Canvas?.DrawRect(bounds, resultPaint);
}
}

private protected override void DisposeInternal()
Expand All @@ -48,7 +50,6 @@ private protected override void DisposeInternal()

_sourcePaint?.Dispose();
_maskPaint?.Dispose();
_resultPaint?.Dispose();
}
}
}
91 changes: 41 additions & 50 deletions src/Uno.UI.Composition/Composition/CompositionSpriteShape.skia.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,13 @@
using Windows.Foundation;
using SkiaSharp;
using Uno;
using Uno.Disposables;
using Uno.Extensions;

namespace Microsoft.UI.Composition
{
public partial class CompositionSpriteShape : CompositionShape
{
private SKPaint? _strokePaint;
private SKPaint? _fillPaint;
private SKPath? _geometryWithTransformations;

internal override void Paint(in Visual.PaintingSession session)
Expand All @@ -19,7 +18,7 @@ internal override void Paint(in Visual.PaintingSession session)
{
if (FillBrush is { } fill)
{
var fillPaint = TryCreateAndClearFillPaint(session.Filters.OpacityColorFilter);
using var fillPaintDisposable = GetTempFillPaint(session.Filters.OpacityColorFilter, out var fillPaint);

if (Compositor.TryGetEffectiveBackgroundColor(this, out var colorFromTransition))
{
Expand Down Expand Up @@ -52,8 +51,8 @@ internal override void Paint(in Visual.PaintingSession session)

if (StrokeBrush is { } stroke && StrokeThickness > 0)
{
var fillPaint = TryCreateAndClearFillPaint(session.Filters.OpacityColorFilter);
var strokePaint = TryCreateAndClearStrokePaint(session.Filters.OpacityColorFilter);
using var fillPaintDisposable = GetTempFillPaint(session.Filters.OpacityColorFilter, out var fillPaint);
using var strokePaintDisposable = GetTempStrokePaint(session.Filters.OpacityColorFilter, out var strokePaint);

// Set stroke thickness
strokePaint.StrokeWidth = StrokeThickness;
Expand All @@ -78,59 +77,45 @@ internal override void Paint(in Visual.PaintingSession session)
// On Windows, the stroke is simply 1px, it doesn't scale with the height.
// So, to get a correct stroke geometry, we must apply the transformations first.

// Get the stroke geometry, after scaling has been applied.
var strokeGeometry = strokePaint.GetFillPath(geometryWithTransformations);
using (SkiaHelper.GetTempSKPath(out var strokeFillPath))
{
// Get the stroke geometry, after scaling has been applied.
strokePaint.GetFillPath(geometryWithTransformations, strokeFillPath);

stroke.UpdatePaint(fillPaint, strokeGeometry.Bounds);
stroke.UpdatePaint(fillPaint, strokeFillPath.Bounds);

session.Canvas.DrawPath(strokeGeometry, fillPaint);
session.Canvas.DrawPath(strokeFillPath, fillPaint);
}
}
}
}

private SKPaint TryCreateAndClearStrokePaint(SKColorFilter? colorFilter)
=> TryCreateAndClearPaint(ref _strokePaint, true, colorFilter, CompositionConfiguration.UseBrushAntialiasing);
private DisposableStruct<SKPaint> GetTempStrokePaint(SKColorFilter? colorFilter, out SKPaint paint)
=> GetTempPaint(out paint, true, colorFilter, CompositionConfiguration.UseBrushAntialiasing);

private SKPaint TryCreateAndClearFillPaint(SKColorFilter? colorFilter)
=> TryCreateAndClearPaint(ref _fillPaint, false, colorFilter, CompositionConfiguration.UseBrushAntialiasing);
private DisposableStruct<SKPaint> GetTempFillPaint(SKColorFilter? colorFilter, out SKPaint paint)
=> GetTempPaint(out paint, false, colorFilter, CompositionConfiguration.UseBrushAntialiasing);

private static SKPaint TryCreateAndClearPaint(ref SKPaint? paint, bool isStroke, SKColorFilter? colorFilter, bool isHighQuality = false)
private static DisposableStruct<SKPaint> GetTempPaint(out SKPaint paint, bool isStroke, SKColorFilter? colorFilter, bool isHighQuality = false)
{
if (paint == null)
{
// Initialization
paint = new SKPaint();
paint.IsStroke = isStroke;
paint.IsAntialias = true;
paint.IsAutohinted = true;
var disposable = SkiaHelper.GetTempSKPaint(out paint);
paint.IsAntialias = true;
paint.ColorFilter = colorFilter;

if (isHighQuality)
{
paint.FilterQuality = SKFilterQuality.High;
}
}
else
{
// Cleanup
// - Brushes can change, we cant leave color and shader garbage
// from last rendering around for the next pass.
paint.Color = SKColors.White; // Transparent color wouldn't draw anything
if (paint.Shader is { } shader)
{
shader.Dispose();
paint.Shader = null;
}
paint.IsStroke = isStroke;

if (paint.PathEffect is { } pathEffect)
{
pathEffect.Dispose();
paint.PathEffect = null;
}
}
// uno-specific defaults
paint.Color = SKColors.White; // Transparent color wouldn't draw anything
paint.IsAutohinted = true;
// paint.IsAntialias = true; // IMPORTANT: don't set this to true by default. It breaks canvas clipping on Linux for some reason.

paint.ColorFilter = colorFilter;
if (isHighQuality)
{
paint.FilterQuality = SKFilterQuality.High;
}

return paint;
return disposable;
}

private protected override void OnPropertyChangedCore(string? propertyName, bool isSubPropertyChange)
Expand Down Expand Up @@ -175,14 +160,20 @@ internal override bool HitTest(Point point)
return true;
}

if (StrokeBrush is { } stroke && StrokeThickness > 0)
if (StrokeBrush is { } && StrokeThickness > 0)
{
var strokePaint = TryCreateAndClearStrokePaint(null);
strokePaint.StrokeWidth = StrokeThickness;
var strokeGeometry = strokePaint.GetFillPath(geometryWithTransformations);
if (strokeGeometry.Contains((float)point.X, (float)point.Y))
using (GetTempStrokePaint(null, out var strokePaint))
{
return true;
strokePaint.StrokeWidth = StrokeThickness;

using (SkiaHelper.GetTempSKPath(out var hitTestStrokeFillPath))
{
strokePaint.GetFillPath(geometryWithTransformations, hitTestStrokeFillPath);
if (hitTestStrokeFillPath.Contains((float)point.X, (float)point.Y))
{
return true;
}
}
}
}
}
Expand Down
22 changes: 13 additions & 9 deletions src/Uno.UI.Composition/Composition/ShapeVisual.skia.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
#nullable enable

using System;
using System.Numerics;
using Windows.Foundation;
using SkiaSharp;
Expand All @@ -12,9 +13,12 @@ public partial class ShapeVisual
private protected override void ApplyPrePaintingClipping(in SKCanvas canvas)
{
base.ApplyPrePaintingClipping(in canvas);
if (GetViewBoxPathInElementCoordinateSpace() is { } path)
using (SkiaHelper.GetTempSKPath(out var prePaintingClipPath))
{
canvas.ClipPath(path, antialias: true);
if (GetViewBoxPathInElementCoordinateSpace(prePaintingClipPath))
{
canvas.ClipPath(prePaintingClipPath, antialias: true);
}
}
}

Expand All @@ -32,28 +36,28 @@ internal override void Paint(in PaintingSession session)
base.Paint(in session);
}

internal SKPath? GetViewBoxPathInElementCoordinateSpace()
/// <returns>true if a ViewBox exists</returns>
internal bool GetViewBoxPathInElementCoordinateSpace(SKPath dst)
{
if (ViewBox is not { } viewBox)
{
return null;
return false;
}

var shape = new SKPath();
dst.Rewind();
var clipRect = new SKRect(viewBox.Offset.X, viewBox.Offset.Y, viewBox.Offset.X + viewBox.Size.X, viewBox.Offset.Y + viewBox.Size.Y);
shape.AddRect(clipRect);
dst.AddRect(clipRect);
if (viewBox.IsAncestorClip)
{
Matrix4x4.Invert(TotalMatrix, out var totalMatrixInverted);
var childToParentTransform = Parent!.TotalMatrix * totalMatrixInverted;
if (!childToParentTransform.IsIdentity)
{

shape.Transform(childToParentTransform.ToSKMatrix());
dst.Transform(childToParentTransform.ToSKMatrix());
}
}

return shape;
return true;
}

/// <remarks>This does NOT take the clipping into account.</remarks>
Expand Down
33 changes: 33 additions & 0 deletions src/Uno.UI.Composition/Composition/SkiaHelper.skia.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
#nullable enable

using SkiaSharp;
using Microsoft.CodeAnalysis.PooledObjects;
using Uno.Disposables;
using SKPaint = SkiaSharp.SKPaint;

namespace Microsoft.UI.Composition
{
internal static class SkiaHelper
{
private static readonly ObjectPool<SKPaint> _paintPool = new(() => new SKPaint(), 8);
private static readonly ObjectPool<SKPath> _pathPool = new(() => new SKPath(), 8);

public static DisposableStruct<SKPath> GetTempSKPath(out SKPath path)
{
path = _pathPool.Allocate();
// Note the difference between Rewind and Reset
// https://api.skia.org/classSkPath.html#a8dc858ee4c95a59b3dd4bdd3f7b85fdc : "Use rewind() instead of reset() if SkPath storage will be reused and performance is critical."
path.Rewind();
return new DisposableStruct<SKPath>(p => _pathPool.Free(p), path);
}

public static DisposableStruct<SKPaint> GetTempSKPaint(out SKPaint paint)
{
paint = _paintPool.Allocate();
// https://api.skia.org/classSkPaint.html#a6c7118c97a0e8819d75aa757afbc4c49
// "This is equivalent to replacing SkPaint with the result of SkPaint()."
paint.Reset();
return new DisposableStruct<SKPaint>(p => _paintPool.Free(p), paint);
}
}
}
10 changes: 6 additions & 4 deletions src/Uno.UI/UI/Xaml/Controls/TextBlock/TextBlock.skia.cs
Original file line number Diff line number Diff line change
Expand Up @@ -188,11 +188,13 @@ partial void SetupInlines()
{
var canvas = t.canvas;
var rect = t.rect;
canvas.DrawRect(new SKRect((float)rect.Left, (float)rect.Top, (float)rect.Right, (float)rect.Bottom), new SKPaint

using (SkiaHelper.GetTempSKPaint(out var paint))
{
Color = SelectionHighlightColor.Color.ToSKColor(),
Style = SKPaintStyle.Fill
});
paint.Color = SelectionHighlightColor.Color.ToSKColor();
paint.Style = SKPaintStyle.Fill;
canvas.DrawRect(new SKRect((float)rect.Left, (float)rect.Top, (float)rect.Right, (float)rect.Bottom), paint);
}
};

_inlines.RenderSelection = IsTextSelectionEnabled;
Expand Down
12 changes: 7 additions & 5 deletions src/Uno.UI/UI/Xaml/Controls/TextBox/TextBox.skia.cs
Original file line number Diff line number Diff line change
Expand Up @@ -239,11 +239,13 @@ private void UpdateTextBoxView()
var caretRect = args.rect;
var compositor = _visual.Compositor;
var brush = DefaultBrushes.TextForegroundBrush.GetOrCreateCompositionBrush(compositor);
var caretPaint = new SKPaint(); // we create a new caret everytime to dodge the complications that occur when trying to "reset" an SKPaint.
brush.UpdatePaint(caretPaint, caretRect.ToSKRect());
args.canvas.DrawRect(
new SKRect((float)caretRect.Left, (float)caretRect.Top, (float)caretRect.Right,
(float)caretRect.Bottom), caretPaint);
using (SkiaHelper.GetTempSKPaint(out var caretPaint))
{
brush.UpdatePaint(caretPaint, caretRect.ToSKRect());
args.canvas.DrawRect(
new SKRect((float)caretRect.Left, (float)caretRect.Top, (float)caretRect.Right,
(float)caretRect.Bottom), caretPaint);
}
}

if ((CaretMode == CaretDisplayMode.CaretWithThumbsOnlyEndShowing && args.endCaret) ||
Expand Down
16 changes: 0 additions & 16 deletions src/Uno.UI/UI/Xaml/Documents/Inline.skia.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,22 +10,6 @@ namespace Microsoft.UI.Xaml.Documents
partial class Inline
{
private FontDetails? _fontInfo;
private SKPaint? _paint;

internal SKPaint Paint
{
get
{
var paint = _paint ??= new SKPaint()
{
TextEncoding = SKTextEncoding.Utf16,
IsStroke = false,
IsAntialias = true,
};

return paint;
}
}

internal FontDetails FontInfo
{
Expand Down
Loading

0 comments on commit b6333bd

Please sign in to comment.