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

Maui in Android Overdraws even with blank project #18245

Open
jonmdev opened this issue Oct 23, 2023 · 7 comments · May be fixed by #22126
Open

Maui in Android Overdraws even with blank project #18245

jonmdev opened this issue Oct 23, 2023 · 7 comments · May be fixed by #22126
Labels
legacy-area-perf Startup / Runtime performance platform/android 🤖 t/enhancement ☀️ New feature or request t/perf The issue affects performance (runtime speed, memory usage, startup time, etc.)
Milestone

Comments

@jonmdev
Copy link

jonmdev commented Oct 23, 2023

Description

Android publishes a guide here explaining how to Debug performance problems: https://developer.android.com/topic/performance/rendering/inspect-gpu-rendering

Maui does not nearly perform as well as it should in Android even with the most basic setups. Likely some of this is due to bugs with overdraw.

As they show on that page, if you turn on Developer Options on an Android device, and build to it, you can also activate "Debug GPU Overdraw" mode which can show areas of overdraw on screen in real time.

Interpretation: (shown on page linked above)

  • True color: No overdraw
  • Blue: Overdrawn 1 time
  • Green: Overdrawn 2 times
  • Pink: Overdrawn 3 times
  • Red: Overdrawn 4 or more times

MAUI OVERDRAWS EVEN IN EMPTY PROJECTS
If you even just have an empty ContentPage as the only entry in your app, by making App.xaml.cs read:

    public App() {
        InitializeComponent();

        ContentPage mainPage = new();
        MainPage = mainPage;

You will get an overdrawn result.

This is the default Maui project showing full screen overdraw x1 (bot icon is thus overdrawn x2):

Overdraw Bug Default Project

This is the empty project with just a blank ContentPage showing as you normally see it from a user's perspective:
Overdraw empty page

Here it is with overdraw mode showing it is being overdrawn:
Overdraw empty page blue

OTHER OBJECTS
I wrote the test project as below to also test how the overdraw is affected by different objects. Any empty object with no background or coloration should not cause overdraw. Image and Layout respected this and did not increase overdraw as long as they had no background.

However, default Border and BoxView elements do increase overdraw even with no backgrounds. I suspect this is not normal behavior. I am not sure if perhaps it is due to the default stroke of the Border in its case or what the cause for the BoxView would be. Or if it is related to the underlying overdraw even in empty projects.

ISSUE
Every bit of overdraw is an entire extra layer of full screen rendering the system is doing per frame cutting down our ability to render what we actually need to by 1/4-1/3 or 1/2. If we can at least get empty projects not overdrawing this would be good and it may boost our performance considerably.

It would perhaps get us closer to our frame rates we need to hit which are currently not matching what should be expected otherwise.

I have to assume it is something intrinsic to ContentPage or the App objects as it overdraws even when they are the only things there are in the project. Thanks for any help.

Steps to Reproduce

  1. Put Android device into Developer Mode as per: https://www.lifewire.com/how-to-enable-developer-mode-on-android-4684044
  2. Search in settings for Debug GPU overdraw and activate it as per: https://developer.android.com/topic/performance/rendering/inspect-gpu-rendering
  3. Open test project, use bool, int, and Type switches to test a few configurations as follows to see it overdraws even with empty projects:
namespace Overdraw_Bug {
    public partial class App : Application {
        public App() {
            InitializeComponent();

            ContentPage mainPage = new();
            MainPage = mainPage;

            //=======================
            //ABNORMAL BEHAVIORS
            //=======================
            //See: https://developer.android.com/topic/performance/rendering/inspect-gpu-rendering
            //1) Empty project overdraws by 1 (blue screen with overdraw mode activated in Android phone) - set "addMoreItems = false" and build to see - should be white screen.
            //2) Adding Border/BoxView elements increase overdraw even if they have no background, while Layout/Image elements do not (things without backgrounds shouldn't cause overdraw)

            //===========================================
            //OPTIONALLY ADD MORE ITEMS TO SEE RESULT
            //===========================================
            bool addMoreItems = true; //set false and can still see screen overdraws on nothing
            Type typeToAdd = typeof(AbsoluteLayout); //change type to add here (Border/BoxView increase overdraw even when empty, Layout/Image do not)
            bool colorizeBg = false; //add color to Bg or not
            int numToAdd = 3;

            //adding more layout objects overtop does not worsen the overdraw as long as they have no backgrounds
            List<VisualElement> veList = new();
            if (addMoreItems) {

                AbsoluteLayout abs = new();
                mainPage.Content = abs;

                for (int i = 0; i < numToAdd; i++) {
                    VisualElement ve = (VisualElement)Activator.CreateInstance(typeToAdd);
                    if (colorizeBg) { ve.BackgroundColor = Colors.White; }
                    veList.Add(ve);
                    abs.Add(ve);
                }
            }

            //============================
            //SCREEN RESIZE FUNCTION
            //============================
            mainPage.SizeChanged += delegate {
                double width = mainPage.Width;
                double height = mainPage.Height;
                
                if (width > 0) {

                    for (int i=0; i < veList.Count; i++) {
                        veList[i].WidthRequest = width;
                        veList[i].HeightRequest = height;

                    }
                }
            };
        }
    }
}

Link to public reproduction project repository

https://github.com/jonmdev/Overdraw-Bug

Version with bug

7.0.96

Is this a regression from previous behavior?

No, this is something new

Last version that worked well

Unknown/Other

Affected platforms

Android

Did you find any workaround?

No. Tough to hit performance targets in Maui than should be expected, persistent overdraw likely contributing.

@jonmdev jonmdev added the t/bug Something isn't working label Oct 23, 2023
@jsuarezruiz jsuarezruiz added platform/android 🤖 legacy-area-perf Startup / Runtime performance labels Oct 23, 2023
@jsuarezruiz
Copy link
Contributor

cc @jonathanpeppers

@jonathanpeppers
Copy link
Member

@jonmdev beyond making the overdraw colors "go away". Do you know how to measure how much fixing these actually helps?

Making overdraw better helps, but does it help like 1%, 10%? How do we measure?

I'm trying to gage how important this is compared to just profiling and making general C# code changes.

@jonathanpeppers jonathanpeppers added t/enhancement ☀️ New feature or request and removed t/bug Something isn't working labels Oct 23, 2023
@jonmdev
Copy link
Author

jonmdev commented Oct 23, 2023

Thanks for your attention @jonathanpeppers

Regarding the importance, here is Android's documentation on it:

Do not draw too many layers on top of each other.
Remove any views that are completely obscured by other opaque views on top of it.
If you need to draw several layers blended on top of each other, consider merging them into a single layer.
A good rule of thumb with current hardware is to not draw more than 2.5 times the number of pixels on screen per frame (transparent pixels in a bitmap count!).

https://developer.android.com/topic/performance/hardware-accel

In other words, if we are already overdrawing the entire screen in an empty project we are at 2x the number of pixels on screen per frame to begin with. Adding anything else puts us over Android's recommended amount. (Although it seems as noted below this test process has found some much bigger problems...)

Maui is a GUI system by definition so letting the GUI meet expected Native performance standards and not already be overdrawn on an empty project is important I think if possible. Having reasonable redraw performance is important, but as we see below there are perhaps more significant issues still.

PARTIAL SOLUTION: FOUND 1 OF THE OVERDRAWS

I have found one of the two draws that is happening in the empty project of just:

public App() {
        InitializeComponent();

        ContentPage mainPage = new();
        MainPage = mainPage;
 
}

ContentPage creates a backgroundColor automatically by default. If you set it with mainPage.BackgroundColor = Colors.White the screen remains overdrawn x1 (nothing changes - indicating this is default state).

If you add mainPage.BackgroundColor = null you get a white project with no overdraw (screen is being drawn only once - correct behavior, but we can't control the color of the remaining unknown draw so it is wasteful) - as soon as we add a background color where we can we are again overdrawing.

Here it is with mainPage.BackgroundColor = null showing no overdraw, but also not letting us then set the background for anything without overdrawing:

overdraw - main bg null

OTHER MYSTERY DRAW???

This answers 1 of the two draws.

What might the second draw be? It is obviously something outside my control in Maui. I can't think of anything else I can control.

Is it possible to debug the Android hierarchy and see what is drawing? How many elements in Android can there be on an empty project like this? I can't imagine more than 5-20.

Certainly at least one of them is drawing itself somewhere and doesn't need to be. Or perhaps this hidden element must be drawn to as we always have to draw something on each pixel of the app by default?

If we must have the "hidden background object" being drawn to, then an easy solution would be to give us a method to set its BackgroundColor so at least we can use that as our app backgrounds and not have to waste an object and overdraw by default.

PERFORMANCE

Here is Android showing the project with just tweening the background color (default settings of modified demo project code as below). This is overdraw x1 and attempting to change the color of the element mainPage only at 60 fps on a high end 120 Hz Samsung device in release mode:

overdraw photo of screen just bg

The colors of the bars at the bottom are explained as per: https://developer.android.com/topic/performance/rendering/inspect-gpu-rendering

The GPU is getting hammered just from this simple operation with most of the work going to red (Command Issue) and yellow (Swap Buffers). It is barely keeping up below the yellow threshold. For reference, the green threshold is 60 fps (or possible 120 fps since this is a 120 hz device?) so either way it is failing to even meet the device refresh rate.

Adding a few overdraws then pushes it even mildly higher over this threshold:
overdraw - photo of screen overdrawn

Thanks for any help.

@jonmdev jonmdev changed the title Maui Overdraws in Android even with completely blank or default project, also overdraws with empty Borders and BoxViews, hurting frame rates and performance targets Maui in Android: (1) Overdraws even with blank project, (2) Stutters from massive periodic Swap Buffer spikes, (3) Requires very high graphics utilization for even small visual changes Oct 24, 2023
@PureWeen PureWeen added this to the Backlog milestone Oct 24, 2023
@ghost
Copy link

ghost commented Oct 24, 2023

We've added this issue to our backlog, and we will work to address it as time and resources allow. If you have any additional information or questions about this issue, please leave a comment. For additional info about issue management, please read our Triage Process.

@jonmdev jonmdev changed the title Maui in Android: (1) Overdraws even with blank project, (2) Stutters from massive periodic Swap Buffer spikes, (3) Requires very high graphics utilization for even small visual changes Maui in Android Overdraws even with blank project Mar 7, 2024
@jonmdev
Copy link
Author

jonmdev commented Mar 29, 2024

I found the missing overdraw. It is a white background (#FFFFFF) being applied to the DecorView, which you can only see on Android Layout viewer when it loads in "Legacy Mode" which it only does by glitch sometimes it seems (usually doesn't show you this).

background white on 'legacy' mode

You have to run the following command to solve it:

mainPage.HandlerChanged += delegate {
        Platform.CurrentActivity.Window.DecorView.SetBackground(null); //=============KILLS THE EXTRA DRAW
}

FYI @jonathanpeppers if you want to fix it.

I also note a small quirk (though I haven't investigated further to be sure) that when you set the ContentPage background color, you cannot null it using Maui command in Android. You similarly must get the view and SetBackground(null). Perhaps I am wrong on this. Not important to me now.

What matters is it is now possible to get zero overdraw using this method.

@jonathanpeppers
Copy link
Member

jonathanpeppers commented Mar 29, 2024

Android Studio's layout inspector might be a better choice to debug this.

@jonmdev have you tested a Release app to see if is any different? I searched the source code for DecorView:

I'm not seeing any MAUI feature that the project template uses that would do DecorView.SetBackground(Color.White).

WindowOverlay, I think is something used by VS diagnostics / Hot Reload. I wonder if the overdraw is gone if you are running a Release app with no VS attached?

This one is on my list to look at, but I'm going on vacation soon. It might be a couple weeks when I get to this.

@jonmdev
Copy link
Author

jonmdev commented Mar 29, 2024

Android Studio's layout inspector might be a better choice to debug this.

@jonmdev have you tested a Release app to see if is any different? I searched the source code for DecorView:

* https://github.com/search?q=repo%3Adotnet%2Fmaui%20DecorView&type=code

I'm not seeing any MAUI feature that the project template uses that would do DecorView.SetBackground(Color.White).

WindowOverlay, I think is something used by VS diagnostics / Hot Reload. I wonder if the overdraw is gone if you are running a Release app with no VS attached?

This one is on my list to look at, but I'm going on vacation soon. It might be a couple weeks when I get to this.

I have tested and the issues occur the same whether or not you build on Release or Debug.

I don't believe Maui is intentionally setting the DecorView background color to white. This could even be an Android issue. I am not sure. Either way adding the line Platform.CurrentActivity.Window.DecorView.SetBackground(null); at some point solves it, so that is easy enough.

The screencap I provided of the white DecorView was from Android Studio Layout Viewer which randomly at some points loads in "Legacy Mode" and shows this format of hierarchy which demonstrates the problem. I don't know why it loads in two different hierarchy possibilities. But when it loaded this way I could see the solution. (Usually it doesn't show the DecorView in Android Studio Layout Viewer as screencapped there.)

I have also tested again and proven that yes, if you set the ContentPage's background color to anything, then set it to null after it will still overdraw subsequently unless you use the Android method SetBackground(null) to reverse it. This is the correct way to null a background in Android and is likely not being applied. This may affect other view types too (not sure didn't test other view tests).

I figured out also that BoxView and Border are I believe hardware layers so will draw (contribute to overdraw) even if empty. That is normal then.

I updated my demo project to the following fix demonstration:

            mainPage.HandlerChanged += delegate {
                mainPage.BackgroundColor = null; // DOES NOT WORK IF BACKGROUND COLOR WAS PREVIOUSLY SET IN ANDROID, STILL OVEDRAWS UNLESS YOU USE ANDROID METHOD BELOW
#if ANDROID

                //====================================================================
                //IF YOU COMMENT OUT EITHER OF THESE LINES YOU WILL GET OVERDRAW
                //====================================================================
                mainPage.ToPlatform(mainPage.Handler.MauiContext).SetBackground(null); //NECESSARY TO UNDO THE BAKCGROUND COLOR IF IT WAS SET BEFORE 
                Platform.CurrentActivity.Window.DecorView.SetBackground(null); //===============SOLVES THE EXTRA DRAW

#endif
            };
        }

By default it will load the project with no overdraw with one visible AbsoluteLayout with a white background, but only because of the handler changed code where:

  1. I am using Android nulling of the mainPage's background
  2. I am setting DecorView background to null with Android.

If you comment out each of these lines you will get overdraw.

I would suggest:

  • Add the DecorView line somewhere in the default window construction or app construction for Android somewhere.
  • Ensure View.BackgroundColor = null in Maui uses Android.Views.View.SetBackground(null) in background always (only way to null backgrounds in Android)
  • Probably okay for ContentPages to have white backgrounds by default as without this the app loads black which is less expected perhaps for people.

I think that should do it.

At this point, I consider this solved for me. It has improved my app efficiency eliminating the overdraw.

I am at this point much more interested if you have any comment at all on this issue. I have posted now three bug reports about it over the past 6 months with the most detailed investigations I can perform, but I have yet to receive even a single comment from anyone in your team about the issue or even suggestions on what to look at next.

This is a much bigger problem than this overdraw issue, as overdraw is at least invisible (besides performance) to the user. Layout problems and glitches are very obvious to everyone.

Do you have any thoughts on it? Is there any more information I can provide or anything else I should do?

If you would like my .NET Android project mentioned in that thread proving the relevant functions all work as expected in Android, I am happy to share that too.

Thanks for any thoughts at all.

jonathanpeppers added a commit to jonathanpeppers/maui that referenced this issue Apr 29, 2024
Fixes: dotnet#18245
Context: https://developer.android.com/studio/debug/dev-options#hardware
Context: https://developer.android.com/topic/performance/rendering/inspect-gpu-rendering#debug_overdraw
Context: https://stackoverflow.com/q/6499004

There is `Debug GPU Overdraw` developer option in Android that can be
enabled to see overdraw in the app.

* Blue: overdrawn by 1
* Green: overdrawn by 2
* Pink: overdrawn by 3
* Red: overdrawn by 4+

The .NET MAUI project template shows `Blue` by default, in a project
with nothing on the screen. A workaround is noted on dotnet#18245, but the
*best* workaround would be in `Platforms\Android\MainActivity.cs`:

    protected override void OnCreate(Bundle? savedInstanceState)
    {
        base.OnCreate(savedInstanceState);
        // Clear the Window's background
        Window?.SetBackgroundDrawable(null);
    }

With this change in place, the `Blue` overdraw is gone in the .NET
MAUI project template.

The result is .NET MAUI apps on Android should inherently use less GPU
(the size of the screen), which could be noticeable on low-end devices.
It could be particularly noticeable in apps that are moving into the
`Red` range based on the developer's XAML & layout choices.

We can make this same change in .NET MAUI's default `Maui.MainTheme`
Android theme:

    <item name="android:windowBackground">@null</item>

The default project template has a XAML style that sets the background:

    <Style TargetType="Page" ApplyToDerivedTypes="True">
        <Setter Property="BackgroundColor" Value="{AppThemeBinding Light={StaticResource White}, Dark={StaticResource OffBlack}}" />
    </Style>

So the only case this change would be noticed (wrong color), would be if:

* Developer is using a `Page` with no `Background` set, and no
  `Background` set on the top-most layout.

* Developer removed all XAML styles or is migrating a Xamarin.Forms app

In this case, a developer could do one of:

* Apply a `Background` to the `Page`

* Extend the `Maui.MainTheme` style, but with a `Background` set
jonathanpeppers added a commit to jonathanpeppers/maui that referenced this issue May 7, 2024
Fixes: dotnet#18245
Context: https://developer.android.com/studio/debug/dev-options#hardware
Context: https://developer.android.com/topic/performance/rendering/inspect-gpu-rendering#debug_overdraw
Context: https://stackoverflow.com/q/6499004

There is `Debug GPU Overdraw` developer option in Android that can be
enabled to see overdraw in the app.

* Blue: overdrawn by 1
* Green: overdrawn by 2
* Pink: overdrawn by 3
* Red: overdrawn by 4+

The .NET MAUI project template shows `Blue` by default, in a project
with nothing on the screen. A workaround is noted on dotnet#18245, but the
*best* workaround would be in `Platforms\Android\MainActivity.cs`:

    protected override void OnCreate(Bundle? savedInstanceState)
    {
        base.OnCreate(savedInstanceState);
        // Clear the Window's background
        Window?.SetBackgroundDrawable(null);
    }

With this change in place, the `Blue` overdraw is gone in the .NET
MAUI project template.

The result is .NET MAUI apps on Android should inherently use less GPU
(the size of the screen), which could be noticeable on low-end devices.
It could be particularly noticeable in apps that are moving into the
`Red` range based on the developer's XAML & layout choices.

We can make this same change in .NET MAUI's default `Maui.MainTheme`
Android theme:

    <item name="android:windowBackground">@null</item>

The default project template has a XAML style that sets the background:

    <Style TargetType="Page" ApplyToDerivedTypes="True">
        <Setter Property="BackgroundColor" Value="{AppThemeBinding Light={StaticResource White}, Dark={StaticResource OffBlack}}" />
    </Style>

So the only case this change would be noticed (wrong color), would be if:

* Developer is using a `Page` with no `Background` set, and no
  `Background` set on the top-most layout.

* Developer removed all XAML styles or is migrating a Xamarin.Forms app

In this case, a developer could do one of:

* Apply a `Background` to the `Page`

* Extend the `Maui.MainTheme` style, but with a `Background` set
@Eilon Eilon added the t/perf The issue affects performance (runtime speed, memory usage, startup time, etc.) label May 10, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
legacy-area-perf Startup / Runtime performance platform/android 🤖 t/enhancement ☀️ New feature or request t/perf The issue affects performance (runtime speed, memory usage, startup time, etc.)
Projects
None yet
Development

Successfully merging a pull request may close this issue.

5 participants