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

[.NET 8 & .NET 7] Poor performance in scrolling in the image list. #18877

Open
cropyai opened this issue Nov 19, 2023 · 17 comments
Open

[.NET 8 & .NET 7] Poor performance in scrolling in the image list. #18877

cropyai opened this issue Nov 19, 2023 · 17 comments
Labels
area-layout StackLayout, GridLayout, ContentView, AbsoluteLayout, FlexLayout, ContentPresenter platform/android 🤖 s/needs-attention Issue has more information and needs another look s/triaged Issue has been reviewed s/verified Verified / Reproducible Issue ready for Engineering Triage t/bug Something isn't working
Milestone

Comments

@cropyai
Copy link

cropyai commented Nov 19, 2023

Description

Hi,

I prepared an example project. You can find it below:

MauiApp10.zip

The problem with this project is that while scrolling the images, the animation or scrolling is not smooth enough. The frames are stuttered even though I have a device with Snapdragon Gen 2 which is the most powerful processor. This is happening in .NET 8 and also was happening in .NET 7 as well. It is too bad that we just cannot scroll a list of images.

Video

Here is an example video showing the issue below. The video does not show how severe the stutter is. By looking at the real phone with naked eyes, the poor performance is much much more noticeable.

x3.mp4

Code

I used Binding to create this example project.

OrderCard ContentView:

public partial class OrderCard : ContentView
{
    public static readonly BindableProperty OrderProperty =
        BindableProperty.Create(nameof(Order), typeof(OrderModel), typeof(OrderCard), new OrderModel());

    public OrderModel Order
    {
        get => (OrderModel)GetValue(OrderProperty);
        set => SetValue(OrderProperty, value);
    }

    public OrderCard()
	{
		InitializeComponent();
    }

    private void ContentView_Loaded(object sender, EventArgs e)
    {
        this.image.Source = ImageSource.FromUri(new Uri(Order.ImageUrl));
    }
}

Image Component:
<Image 
WidthRequest="120"
MaximumHeightRequest="90"
    x:Name="image"
    Source="dotnet_bot.png"
    Grid.Column="0" 
    />
    

MainPage:

    public class OrderModel
    {
        public string ImageUrl { get; set; } = "";

    }

    public partial class MainPage : ContentPage
    {
        public ObservableCollection<OrderModel> Orders { get; set; }

        public MainPage()
        {
            InitializeComponent();
            
            Orders = new ObservableCollection<OrderModel>
            {

            };

            this.Loaded += MainPage_Loaded;
            BindingContext = this;
        }

        private void MainPage_Loaded(object? sender, EventArgs e)
        {
            for (int i = 0; i < 10; i++)
            {
                Orders.Add(new OrderModel() { ImageUrl = "https://cropy.ai/assets/images/change-features-1.webp" });
                Orders.Add(new OrderModel() { ImageUrl = "https://cropy.ai/assets/images/change-features-2.webp" });
                Orders.Add(new OrderModel() { ImageUrl = "https://cropy.ai/assets/images/change-features-3.webp" });
                Orders.Add(new OrderModel() { ImageUrl = "https://cropy.ai/assets/images/change-features-4.webp" });
                Orders.Add(new OrderModel() { ImageUrl = "https://cropy.ai/assets/images/change-features-5.webp" });
            }

        }

    }


col_view:
<CollectionView x:Name="col_view" HorizontalOptions="Center" Grid.Row="1" Margin="10,10,10,0" VerticalOptions="Fill"  ItemsSource="{Binding Orders}">
    <CollectionView.ItemTemplate>
        <DataTemplate>
            <controls:OrderCard Margin = "0,0,0,0" Order="{Binding}" />
        </DataTemplate>
    </CollectionView.ItemTemplate>
    <CollectionView.ItemsLayout>
        <GridItemsLayout Orientation="Vertical" Span="1" />
    </CollectionView.ItemsLayout>

</CollectionView>


Steps to Reproduce

No response

Link to public reproduction project repository

No response

Version with bug

8.0.3

Is this a regression from previous behavior?

Not sure, did not test other versions

Last version that worked well

Unknown/Other

Affected platforms

Android, I was not able test on other platforms

Affected platform versions

No response

Did you find any workaround?

No response

Relevant log output

No response

@cropyai cropyai added the t/bug Something isn't working label Nov 19, 2023
@cropyai
Copy link
Author

cropyai commented Nov 19, 2023

I had S23 Ultra with ONE UI 5.0 (android 13.0), and today I updated the phone to ONE UI 6.0 Android 14. The issue about the stuttering seems to be gone for now for the example project on my phone.
//EDIT: I tested with another Android 13.0 device and I can confirm that stuttering is still a valid issue. It is happening definitely!
//EDIT2: On Android 14 device, the stuttering issue happened twice randomly. It randomly happens and when it happens the app is stuttering no matter how long you wait.

I didn't mention earlier that there is another scrolling issue. It still seems to be continuing. I will try to share an example app that will demonstrate the other issue as soon as possible.

@mjsb212
Copy link

mjsb212 commented Nov 20, 2023

I have a Similar issue with CollectionView using GridItemsLayout and Images: #18881

@cropyai
Copy link
Author

cropyai commented Nov 20, 2023

@mjsb212 I find a workaround solution that will work if you don't update the collection view after adding the images. The solution is to wrap a scollview component. This way, the scrolling event is controlled by the scrollview which works better in my opinion. However, if you update the collectionview frequently then the stutter might start again after some point even if you are using a scrollview. It means that the srollview has the same problem probably as the collection view.

<ScrollView 
    Scrolled="scrollview1_Scrolled"
    Grid.Row="1" x:Name="scrollview1"  >
    <CollectionView x:Name="col_view" HorizontalOptions="Center" Margin="10,10,10,0" VerticalOptions="Fill"  ItemsSource="{Binding Orders}">
        <CollectionView.ItemTemplate>
            <DataTemplate>
                <controls:OrderCard Margin = "0,0,0,0" Order="{Binding}" />
            </DataTemplate>
        </CollectionView.ItemTemplate>
        <CollectionView.ItemsLayout>
            <GridItemsLayout Orientation="Vertical" Span="1" />
        </CollectionView.ItemsLayout>

    </CollectionView>
</ScrollView>

@mjsb212
Copy link

mjsb212 commented Nov 21, 2023

@cropyai Thanks, but unfortunately in the official API docs it notes that CollectionView should never be put inside a ScrollView, CollView has its own scroll events & its purpose is to handle the items scrolling and list virtualization -- But if that workaround stops the problem then im thinking there is most likely a problem in the Android recycle view implementation, & so hoping they will fix it soon. I believe @jonathanpeppers made note of the issue and will look into it after Vacation/Holiday. If anyone has any more input please keep us updated!

@Eilon Eilon added the area-layout StackLayout, GridLayout, ContentView, AbsoluteLayout, FlexLayout, ContentPresenter label Nov 21, 2023
@jonathanpeppers
Copy link
Member

@cropyai can you try again with the ScrollView removed? It will cause the CollectionView to expand to infinite height, realizing all the rows.

I filed something about this here: #13368

@jonathanpeppers jonathanpeppers added the s/needs-info Issue needs more info from the author label Nov 27, 2023
@ghost
Copy link

ghost commented Nov 27, 2023

Hi @cropyai. We have added the "s/needs-info" label to this issue, which indicates that we have an open question for you before we can take further action. This issue will be closed automatically in 7 days if we do not hear back from you by then - please feel free to re-open it if you come back to this issue after that time.

@ghost ghost added s/needs-attention Issue has more information and needs another look and removed s/needs-info Issue needs more info from the author labels Nov 27, 2023
@cropyai
Copy link
Author

cropyai commented Nov 27, 2023

@jonathanpeppers @mjsb212 The problem is not related to the scroll view. I removed the scrollview component (it was just a coincidence that the code had scrollview). The problem still occurs in both debug & release modes.
example.zip

@jonathanpeppers
Copy link
Member

We were reading the example code here that uses a CollectionView inside a ScrollView: #18877 (comment)

@cropyai
Copy link
Author

cropyai commented Nov 27, 2023

@jonathanpeppers I am uploading a video about this which is exactly the same case happening in our production app.

@cropyai
Copy link
Author

cropyai commented Nov 27, 2023

@jonathanpeppers here is the video. I recently opened 3-5 issues in this repo. So, If I remember correctly these components call loaded event everytime they reappear in the collectionview. (Now without scrollview, the stuttering lagging issue seems to be gone, but the other issue is still there)

x4.mp4

@cropyai
Copy link
Author

cropyai commented Nov 27, 2023

@jonathanpeppers please read my comment on #18881 .

My comment was as below:

@jsuarezruiz @jonathanpeppers as you see above, I have the same similar issue referenced. I can also share some experience with another problem. It only happens in specific design in xaml. I have a Contentview which contains some texts and an image. I am using this contentview template in the collection view. When I add enough amount of images and then do scrolling, as content views appear with the scroll, I see a reloading effect for each image. If I am remembering correctly, the loaded event is called for each content view again & again while scrolling. So, it appears that the collectionview is constantly creating new templates and initiate them again and again for each scroll. I don't know where the old templates go.

If I configure xaml page like below:


    <Grid>
        <Grid
            Padding="30,0"
            RowDefinitions="Auto, *"
            >

            <Button 
                x:Name="btn1"
                Grid.Row="0"
                Clicked="Button_Clicked"
                Text="Hello!"
                BackgroundColor="Gray"
                />

            <!--<ScrollView 
                Scrolled="scrollview1_Scrolled"
                x:Name="scrollview1"  >-->
            <CollectionView Grid.Row="1" x:Name="col_view" HorizontalOptions="Center" Margin="10,10,10,0" VerticalOptions="Fill"  ItemsSource="{Binding Orders}">
                    <CollectionView.ItemTemplate>
                        <DataTemplate>
                            <controls:OrderCard Margin = "0,0,0,0" Order="{Binding}" />
                        </DataTemplate>
                    </CollectionView.ItemTemplate>
                    <CollectionView.ItemsLayout>
                        <GridItemsLayout Orientation="Vertical" Span="1" />
                    </CollectionView.ItemsLayout>

                </CollectionView>
            <!--</ScrollView>-->

        </Grid>
    </Grid>

Then the result is this:

x.2.mp4

@kevinxufei
Copy link

请阅读我对#18881的评论。

我的评论如下:

如上所述,我引用了相同的类似问题。我还可以分享一些关于另一个问题的经验。它只发生在 xaml 中的特定设计中。我有一个 Contentview,其中包含一些文本和图像。我在集合视图中使用此内容视图模板。当我添加足够数量的图像然后进行滚动时,当内容视图随滚动一起显示时,我会看到每个图像的重新加载效果。如果我没记错的话,在滚动时,会一次又一次地为每个内容视图调用加载的事件。因此,collectionview 似乎在不断创建新模板,并为每个滚动轴一次又一次地启动它们。我不知道旧模板去哪儿了。

如果我配置xaml页面,如下所示:


    <Grid>
        <Grid
            Padding="30,0"
            RowDefinitions="Auto, *"
            >

            <Button 
                x:Name="btn1"
                Grid.Row="0"
                Clicked="Button_Clicked"
                Text="Hello!"
                BackgroundColor="Gray"
                />

            <!--<ScrollView 
                Scrolled="scrollview1_Scrolled"
                x:Name="scrollview1"  >-->
            <CollectionView Grid.Row="1" x:Name="col_view" HorizontalOptions="Center" Margin="10,10,10,0" VerticalOptions="Fill"  ItemsSource="{Binding Orders}">
                    <CollectionView.ItemTemplate>
                        <DataTemplate>
                            <controls:OrderCard Margin = "0,0,0,0" Order="{Binding}" />
                        </DataTemplate>
                    </CollectionView.ItemTemplate>
                    <CollectionView.ItemsLayout>
                        <GridItemsLayout Orientation="Vertical" Span="1" />
                    </CollectionView.ItemsLayout>

                </CollectionView>
            <!--</ScrollView>-->

        </Grid>
    </Grid>

那么结果是这样的:

x.2.mp4

Verified this issue with Visual Studio 17.10.0 Preview 4(8.0.20). Repro this issue on Samsung S22 (api33) devices.

@kevinxufei kevinxufei added s/verified Verified / Reproducible Issue ready for Engineering Triage s/triaged Issue has been reviewed labels Apr 24, 2024
@jonathanpeppers
Copy link
Member

The first issue I see is:

<Image 
  WidthRequest="120"
  MaximumHeightRequest="90"
  x:Name="image"
  Source="dotnet_bot.png"
  Grid.Column="0" 
/>

dotnet_bot.png is a large image, right? So you are loading it for every row and then replacing it with a different image?

private void ContentView_Loaded(object sender, EventArgs e)
{
    this.image.Source = ImageSource.FromUri(new Uri(Order.ImageUrl));
}

So then I looked at the actual images you are using:

This is a 1485x835 image?!? Can you use a thumbnail image that is more appropriately sized for a CollectionView row?

I would not expect the app to perform well under these circumstances.

How could we better show a warning for these large images, so you would have been aware?

@mattleibow
Copy link
Member

It will be interesting to benchmark this since we have 2 levels of caching that should be happening:

  1. the UriImageSourceService should be caching the downloaded contents
    • Windows caches the bytes internally
    • iOS cached the bytes onto the file system
    • Android uses Glide to cache in several ways
  2. there is an additional optimization on Android where Glide will automatically create smaller images for smaller image views and cache that. We request a specific size from Android and then Glide downloads and creates cached thumbnails to match the desired view size.

But, if this caching is not happening or the cache is not getting hits, then we need to investigate. Is there a perf trace so we can see where the slow things are? Maybe the layouts are doing extra work because sometimes once the image is loaded, a fresh layout pass has to run toi resize the image view to the size of the image.

One way to test would be to set a fixed with and height on the image view and then try scrolling. Also, make sure you are testing without the debugger attached as hot reload does get in the way of things to keep VS refreshed.

@cropyai
Copy link
Author

cropyai commented May 22, 2024

The first issue I see is:

<Image 
  WidthRequest="120"
  MaximumHeightRequest="90"
  x:Name="image"
  Source="dotnet_bot.png"
  Grid.Column="0" 
/>

dotnet_bot.png is a large image, right? So you are loading it for every row and then replacing it with a different image?

No, we don't replace the images. I just want to display N images in a list. It shouldn't matter if they are big or small images. If they are big, so what? Can't I just display big images which is around 720p or 1080p? At the end of the day, a phone shouldn't stutter when viewing images like that.

@cropyai
Copy link
Author

cropyai commented May 22, 2024

Since David Ortinau said he could help fix our company app bugs here I sent him an email as he promised to provide support. No reply from him for months. We have just moved on from using .NET MAUI to ReactNative. Disappointing again

@jonathanpeppers
Copy link
Member

No, we don't replace the images. I just want to display N images in a list. It shouldn't matter if they are big or small images. If they are big, so what? Can't I just display big images which is around 720p or 1080p? At the end of the day, a phone shouldn't stutter when viewing images like that.

My comment is actually suggesting:

  • Don't use a large image, and display it in a small region
  • Use images that match the size they are displayed (as little downscaling as possible)

The large images would be ok, if the rows were sized appropriately (height close to the actual image). Low-end Android GPUs only have about 2x the space to fit their screen resolution.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area-layout StackLayout, GridLayout, ContentView, AbsoluteLayout, FlexLayout, ContentPresenter platform/android 🤖 s/needs-attention Issue has more information and needs another look s/triaged Issue has been reviewed s/verified Verified / Reproducible Issue ready for Engineering Triage t/bug Something isn't working
Projects
None yet
Development

No branches or pull requests

8 participants