CommunityToolkit / Maui

The .NET MAUI Community Toolkit is a community-created library that contains .NET MAUI Extensions, Advanced UI/UX Controls, and Behaviors to help make your life as a .NET MAUI developer easier
https://learn.microsoft.com/dotnet/communitytoolkit/maui
MIT License
2.26k stars 397 forks source link

[BUG] Performance DrawingView degrades with number of lines on the screen #1722

Open Pbasnal opened 8 months ago

Pbasnal commented 8 months ago

Is there an existing issue for this?

Did you read the "Reporting a bug" section on Contributing file?

Current Behavior

I'm writing a handwriting app in MAUI using DrawingView. Below is the sample code of how I'm creating the DrawingView

public MainPage()
{
    InitializeComponent();

    Content = new DrawingView
    {
        Lines = new ObservableCollection<IDrawingLine>(),
        LineColor = Colors.Red,
        LineWidth = 5,
        IsMultiLineModeEnabled = true,

    };
}

This code runs perfectly fine on windows machine but when executed on the android device or emulator, the app starts lagging as the number of lines increases. To be specific, the lag between registering consecutive points increases making the lines look jagged instead of smooth.

I started looking at the code of community toolkit to figure out what is happening and I found out that the DrawingView draws all the lines every time a new point is registered. The code that I feel is causing the issue:

class DrawingViewDrawable : IDrawable
{
    readonly MauiDrawingView drawingView;
    Stopwatch watch = new();

    public DrawingViewDrawable(MauiDrawingView drawingView)
    {
        this.drawingView = drawingView;
    }

    public void Draw(ICanvas canvas, RectF dirtyRect)
    {
        canvas.SetFillPaint(drawingView.Paint, dirtyRect);
        canvas.FillRectangle(dirtyRect);

        drawingView.DrawAction?.Invoke(canvas, dirtyRect);
        DrawCurrentLines(canvas, drawingView);

        SetStroke(canvas, drawingView.LineWidth, drawingView.LineColor);
        canvas.DrawPath(drawingView.currentPath);
    }
}

I'm using CommunityToolkit.Maui 7.0.1 .NET 8 emulator: Pixel 5 - API 34

Also ran on Samsung Tab S6 FE

Video Showing Issue Shows the issue. It is especially visible in the last circle which is drawn. Note that while the circle is being drawn, there is significant lag between where the mouse pointer is and the last point of line is. But more important is the time delay between recording mouse input. That makes the line very jagged. This video is from the sample code which I'm using to debug performance issue.

Recording on Android Device of the app. This recording is of the app that I'm building and it's running on Samsung Tab S6 FE. The drop in performance is visible and it keeps degrading as the number of lines increases.

Expected Behavior

The performance of the DrawingView should stay consistent even if the number of lines increases. Performance here means, the time delay between capturing mouse movement and latency of drawing the line which is being drawn by the user. I believe, we can use an image for this. When user lifts the pointer up, then DrawingView should bake that line on to an image and then just display that image instead of drawing all the lines every time.

Steps To Reproduce

  1. Clone the repo: https://github.com/Pbasnal/todoecs
  2. Go to the folder DrawingViewPerf. This contains the sample code.
  3. Open the solution in visual studio and run the app on Pixel emulator api 34.
  4. It can take a while for the issue to occur so draw a lot of lines, the performance will drop eventually.

Link to public reproduction project repository

https://github.com/Pbasnal/todoecs/tree/main/DrawingViewPerf

Environment

- .NET MAUI CommunityToolkit: 7.0.1
- OS: Windows 11 Home Single Language 23H2 22631.3155 
- .NET MAUI: 7.0.1
- .NET 8

Anything else?

No response

Pbasnal commented 8 months ago

I have made a few changes to the code and updated the repo with 3 different implementations.

  1. The first implementation uses the default DrawingView and experiences performance degradation.
  2. The second implementation uses an Image on which we bake the line and use it as a cache. This way, we don't have to draw all the lines every time. This implementation is experiencing a few problems 2.1 Alignment issue: the generated image doesn't align with the DrawingView canvas. 2.2 We need to create border lines so the entire canvas is captured otherwise only the bounding box of the drawn line is captured. 2.3 Every time the image is updated, the screen flickers.
  3. The third implementation adjusts the points of the border lines using the line width. By doing this, the generated image aligns perfectly with the drawing view canvas. But it still has 2 problems 3.1 Every time the image is updated, the screen flickers. 3.2 The line which gets rendered on the image has a different texture than the line which is drawn by the drawing view.

I'm still trying to figure out the solution for screen flickering as that's more important. Let me know you can help with the screen flicker.

Important note: The screen flickering problem majorly occurs on the emulator and the android device.

bijington commented 8 months ago

I would avoid caching it in a image like you are doing. One possible suggestion that I would try is to have 2 DrawingViews, one on top of the other. Use the top surface to draw the current line and then when it's finished add it to the bottom drawing view

VladislavAntonyuk commented 8 months ago

sorry, but what do you mean with lags? of course, the more lines you have, the longer it takes to render them. but I see that the lines are rather smooth in both of your videos. If I am not mistaken, Rect parameter of Draw method is used to control the rectangle that needs to be updated, so you may not need to redraw the whole canvas, but the line trajectory is not defined, so redrawing the whole canvas should be faster than calculating the dirty part

Pbasnal commented 8 months ago

It might not be as clear in the video since the DrawingView applies smoothening once it has been drawn. The lag can be felt while drawing the line. Below images are from the video Recording on Android Device of the app while the word is being written and smoothening hasn't been applied yet.


smoother Lines pics

In this image, writing experience is fairly good. While writing, the letter "y" is mostly smooth even at very tight angles. I have highlighted one part of the letter where you can see that app didn't register enough points to draw the whole curve and it created a straight line.


But in the below image, the letter "g" has a lot more straight lines instead of the natural curves. Even the round head appears to be square. This makes writing very difficult
Screenshot 2024-03-03 012636

The DrawingViewDrawable code is from the CommunityToolKit and is not part of my code. I just wanted to point it out since I feel that the issue is happening because of that code.

And of course, the more lines a user draws, the longer it'll take to render them. But then the user experience gets worse and that's what I want to fix. I expect the view to provide a consistent experience.

Let me know if DrawingView is not meant for this type of usage and if there is another recommended way to create a view on which user can write or draw.

VladislavAntonyuk commented 8 months ago

If you just need to draw something without storing each point, you can use GraphicsView. DrawingView is designed to allow user get access to each point he draws

Pbasnal commented 8 months ago

I'm making a handwritten note taking app and then perform handwriting to text conversion on the written text. GraphicsView can do that?

VladislavAntonyuk commented 8 months ago

GraphicsView is a canvas where you can draw anything. DrawingView in addition binding all points and lines you drawn

Pbasnal commented 8 months ago

Ok. Let me try that.