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 legacy-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
@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
@Eilon Eilon added the t/perf The issue affects performance (runtime speed, memory usage, startup time, etc.) label May 10, 2024
@samhouts samhouts added the fixed-in-8.0.0-preview.5.8529 Look for this fix in 8.0.0-preview.5.8529! label Aug 2, 2024
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
fixed-in-8.0.0-preview.5.8529 Look for this fix in 8.0.0-preview.5.8529! legacy-area-perf Startup / Runtime performance platform/android 🤖 t/perf The issue affects performance (runtime speed, memory usage, startup time, etc.)
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants