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

[android] reduce interop calls in MauiDrawable #14933

Merged
merged 1 commit into from May 8, 2023

Conversation

jonathanpeppers
Copy link
Member

Context: #12130
Context: https://github.com/angelru/CvSlowJittering

Profiling a .NET MAUI customer sample while scrolling on a Pixel 5, I see some interesting time being spent in:

(0.76%) microsoft.maui!Microsoft.Maui.Graphics.MauiDrawable.OnDraw(Android.Graphics.Drawables.Shapes.Shape,Android.Graphics.Canv
(0.54%) microsoft.maui!Microsoft.Maui.Graphics.MauiDrawable.SetDefaultBackgroundColor()

This sample has a <Border/> inside a <CollectionView/> and so you can see this work happening while scrolling.

Specifically, I found a couple places we had code like:

_borderPaint.StrokeWidth = _strokeThickness;
_borderPaint.StrokeJoin = _strokeLineJoin;
_borderPaint.StrokeCap = _strokeLineCap;
_borderPaint.StrokeMiter = _strokeMiterLimit * 2;
if (_borderPathEffect != null)
    _borderPaint.SetPathEffect(_borderPathEffect);

This calls from C# to Java 5 times. Creating a new method in PlatformInterop.java allowed me to reduce it to 1.

I also found:

void SetDefaultBackgroundColor()
{
    using (var background = new TypedValue())
    {
        if (_context == null || _context.Theme == null || _context.Resources == null)
            return;

        if (_context.Theme.ResolveAttribute(global::Android.Resource.Attribute.WindowBackground, background, true))
        {
            var resource = _context.Resources.GetResourceTypeName(background.ResourceId);
            var type = resource?.ToLowerInvariant();

            if (type == "color")
            {
                var color = new AColor(ContextCompat.GetColor(_context, background.ResourceId));
                _backgroundColor = color;
            }
        }
    }
}

This is doing a lot of unnecessary stuff: looking up a resource by name, etc. I found a very simple Java example we could put in PlatformInterop.java:

https://stackoverflow.com/a/14468034

After these changes, I now see:

(0.28%) microsoft.maui!Microsoft.Maui.Graphics.MauiDrawable.OnDraw(Android.Graphics.Drawables.Shapes.Shape,Android.Graphics.Canv
(0.04%) microsoft.maui!Microsoft.Maui.Graphics.MauiDrawable.SetDefaultBackgroundColor()

This improves the performance of any <Border/> (and other shapes) on Android, and drops about ~1% of the CPU time while scrolling in this example.

Context: dotnet#12130
Context: https://github.com/angelru/CvSlowJittering

Profiling a .NET MAUI customer sample while scrolling on a Pixel 5, I
see some interesting time being spent in:

    (0.76%) microsoft.maui!Microsoft.Maui.Graphics.MauiDrawable.OnDraw(Android.Graphics.Drawables.Shapes.Shape,Android.Graphics.Canv
    (0.54%) microsoft.maui!Microsoft.Maui.Graphics.MauiDrawable.SetDefaultBackgroundColor()

This sample has a `<Border/>` inside a `<CollectionView/>` and so you
can see this work happening while scrolling.

Specifically, I found a couple places we had code like:

    _borderPaint.StrokeWidth = _strokeThickness;
    _borderPaint.StrokeJoin = _strokeLineJoin;
    _borderPaint.StrokeCap = _strokeLineCap;
    _borderPaint.StrokeMiter = _strokeMiterLimit * 2;
    if (_borderPathEffect != null)
        _borderPaint.SetPathEffect(_borderPathEffect);

This calls from C# to Java 5 times. Creating a new method in
`PlatformInterop.java` allowed me to reduce it to 1.

I also found:

    void SetDefaultBackgroundColor()
    {
        using (var background = new TypedValue())
        {
            if (_context == null || _context.Theme == null || _context.Resources == null)
                return;

            if (_context.Theme.ResolveAttribute(global::Android.Resource.Attribute.WindowBackground, background, true))
            {
                var resource = _context.Resources.GetResourceTypeName(background.ResourceId);
                var type = resource?.ToLowerInvariant();

                if (type == "color")
                {
                    var color = new AColor(ContextCompat.GetColor(_context, background.ResourceId));
                    _backgroundColor = color;
                }
            }
        }
    }

This is doing a lot of unnecessary stuff: looking up a resource by
name, etc. I found a very simple Java example we could put in
`PlatformInterop.java`:

https://stackoverflow.com/a/14468034

After these changes, I now see:

    (0.28%) microsoft.maui!Microsoft.Maui.Graphics.MauiDrawable.OnDraw(Android.Graphics.Drawables.Shapes.Shape,Android.Graphics.Canv
    (0.04%) microsoft.maui!Microsoft.Maui.Graphics.MauiDrawable.SetDefaultBackgroundColor()

This improves the performance of any `<Border/>` (and other shapes) on
Android, and drops about ~1% of the CPU time while scrolling in this
example.
Comment on lines -430 to +427
if (canvas == null)
if (canvas == null || _clipPath == null)
return;

var saveCount = canvas.SaveLayer(0, 0, _width, _height, null);

if (_clipPath != null && Paint != null)
canvas.DrawPath(_clipPath, Paint);

if (_clipPath != null && _borderPaint != null)
canvas.DrawPath(_clipPath, _borderPaint);

canvas.RestoreToCount(saveCount);
PlatformInterop.DrawMauiDrawablePath(this, canvas, _width, _height, _clipPath, _borderPaint);
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If _clipPath is null we can return early here, no need to call canvas.SaveLayer/RestoreToCount.

@jonathanpeppers
Copy link
Member Author

jonathanpeppers commented May 4, 2023

@jsuarezruiz @PureWeen is there a sample I can test that it looks exactly the same?

Something with a Border & interesting clipping/shapes, etc.?

@jsuarezruiz
Copy link
Contributor

@jsuarezruiz @PureWeen is there a sample I can test that it looks exactly the same?

Something with a Border & interesting clipping/shapes, etc.?

Launching the .NET MAUI Gallery can use the Control > border samples. Or, for example https://github.com/jsuarezruiz/netmaui-surfing-app-challenge

@Eilon Eilon added the area/perf 🏎️ Startup / Runtime performance label May 5, 2023
@jonathanpeppers
Copy link
Member Author

jonathanpeppers commented May 5, 2023

Ok, I think it looks the same as before, this PR on the left, main on the right:

image

@jonathanpeppers jonathanpeppers marked this pull request as ready for review May 5, 2023 21:14
@jonathanpeppers jonathanpeppers merged commit d8d7c7c into dotnet:main May 8, 2023
29 checks passed
@jonathanpeppers jonathanpeppers deleted the MauiDrawableInterop branch May 8, 2023 16:24
rmarinho pushed a commit that referenced this pull request May 30, 2023
Context: #12130
Context: https://github.com/angelru/CvSlowJittering

Profiling a .NET MAUI customer sample while scrolling on a Pixel 5, I
see some interesting time being spent in:

    (0.76%) microsoft.maui!Microsoft.Maui.Graphics.MauiDrawable.OnDraw(Android.Graphics.Drawables.Shapes.Shape,Android.Graphics.Canv
    (0.54%) microsoft.maui!Microsoft.Maui.Graphics.MauiDrawable.SetDefaultBackgroundColor()

This sample has a `<Border/>` inside a `<CollectionView/>` and so you
can see this work happening while scrolling.

Specifically, I found a couple places we had code like:

    _borderPaint.StrokeWidth = _strokeThickness;
    _borderPaint.StrokeJoin = _strokeLineJoin;
    _borderPaint.StrokeCap = _strokeLineCap;
    _borderPaint.StrokeMiter = _strokeMiterLimit * 2;
    if (_borderPathEffect != null)
        _borderPaint.SetPathEffect(_borderPathEffect);

This calls from C# to Java 5 times. Creating a new method in
`PlatformInterop.java` allowed me to reduce it to 1.

I also found:

    void SetDefaultBackgroundColor()
    {
        using (var background = new TypedValue())
        {
            if (_context == null || _context.Theme == null || _context.Resources == null)
                return;

            if (_context.Theme.ResolveAttribute(global::Android.Resource.Attribute.WindowBackground, background, true))
            {
                var resource = _context.Resources.GetResourceTypeName(background.ResourceId);
                var type = resource?.ToLowerInvariant();

                if (type == "color")
                {
                    var color = new AColor(ContextCompat.GetColor(_context, background.ResourceId));
                    _backgroundColor = color;
                }
            }
        }
    }

This is doing a lot of unnecessary stuff: looking up a resource by
name, etc. I found a very simple Java example we could put in
`PlatformInterop.java`:

https://stackoverflow.com/a/14468034

After these changes, I now see:

    (0.28%) microsoft.maui!Microsoft.Maui.Graphics.MauiDrawable.OnDraw(Android.Graphics.Drawables.Shapes.Shape,Android.Graphics.Canv
    (0.04%) microsoft.maui!Microsoft.Maui.Graphics.MauiDrawable.SetDefaultBackgroundColor()

This improves the performance of any `<Border/>` (and other shapes) on
Android, and drops about ~1% of the CPU time while scrolling in this
example.
@github-actions github-actions bot locked and limited conversation to collaborators Dec 10, 2023
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
area/perf 🏎️ Startup / Runtime performance platform/android 🤖
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants