mono / SkiaSharp

SkiaSharp is a cross-platform 2D graphics API for .NET platforms based on Google's Skia Graphics Library. It provides a comprehensive 2D API that can be used across mobile, server and desktop models to render images.
MIT License
4.39k stars 535 forks source link

[BUG] [WinUI] Scale delay is observed during window resize #2341

Open APopatanasov opened 1 year ago

APopatanasov commented 1 year ago

Description When the window of WinUI application (that has SKCanvasView inside it) is constantly resized the scale of the canvas drawing does not look accurate for some of the frames. That leaves the feeling that the canvas is somehow flickering. The issue can be reproduced with every drawing in the canvas, but it is easily reproduced when you have multiple text drawings on the screen.

After a brief research I've found that the issues was caused by this PR. And more specifically by the following code change: Screenshot 2022-12-15 114944

I can suggest two possible solutions for this issue:

  1. You can bring back the Scale Transform and set the Stretch to None.
  2. Or you can invoke DoInvalidate instead of Invalidate from OnSizeChanged. Invalidate invokes DoInvalidate, but from within a dispatcher which causes the inaccurate rendering of the frames. SizeChanged should be invoked from within the UI thread, so it is safe to invoke DoInvalidate instead of Invalidate.

Code

using SkiaSharp;
using SkiaSharp.Views.Maui;
using SkiaSharp.Views.Maui.Controls;

namespace SkiaScaleWinUI;

public partial class App : Application
{
    public App()
    {
        InitializeComponent();

        MainPage = new TextPage();
    }

    public class TextPage : ContentPage
    {
        public TextPage()
        {
            Title = "Framed Text";

            SKCanvasView canvasView = new SKCanvasView();
            canvasView.PaintSurface += OnCanvasViewPaintSurface;
            Content = canvasView;
        }

        void OnCanvasViewPaintSurface(object sender, SKPaintSurfaceEventArgs args)
        {
            SKSurface surface = args.Surface;
            SKCanvas canvas = surface.Canvas;

            canvas.Clear();

            string str = "Hello SkiaSharp!";
            SKPaint textPaint = new SKPaint { Color = SKColors.Chocolate, TextSize = 24 };

            canvas.DrawText(str, 0, args.Info.Height / 2, textPaint);
        }
    }
}

Expected Behavior

During windows resize the text should not be scaled - at the size and the DPI should be taken into account, but the visual representation should not be affected by this.

Actual Behavior

The text does not look good for some of the frames during window resize.

Basic Information

Screenshots

67 68

Reproduction Link

SkiaScaleWinUI.zip

FrozDark commented 1 year ago

Is it the same https://github.com/mono/SkiaSharp/issues/2295 ?

APopatanasov commented 1 year ago

It looks like to be the same.

bmitc commented 1 year ago

Is it possible that this bug is related to what I describe here in the GLFW forum?

https://discourse.glfw.org/t/canvas-jitter-flicker-when-resizing-with-multithreaded-render-loop/2188/11

I had assumed that the issue I describe there lies within GLFW or somewhere in that stack, although I suppose this could be due to an underlying issue with SkiaSharp.

bmitc commented 1 year ago

Specifically, here's the GIF uploaded to the GLFW support thread.

window-resize-issue

I don't have the ability to test this on macOS, as I don't have an Apple machine, and there is another SkiaSharp bug (https://github.com/mono/SkiaSharp/issues/2350) that is preventing me from drawing to an OpenGL context on Linux, so I can't test there either.

However, I have found out that, at least from what I currently can tell, this issue doesn't occur on every Windows machine. It occurs on two Windows 11 machines that have NVIDIA graphics cards, but from what I can see in my initial testing, I don't see it occur on a Windows 11 machine with only Intel Iris Xe. Of course, the issue is intermittent, so it can be shifty to determine if it's gone. Although it does normally show up somewhat readily.

bmitc commented 1 year ago

@APopatanasov @FrozDark Did either of you ever make any progress on working around this issue? It seems that what you describe is related to what I also encountered in the above video and linked GLFW discussion forum post, where resizing a window causes SkiaSharp to "jitter" even though I redraw as expected on every resize.

I was going to test @APopatanasov's suggested solutions, but I am unable to get SkiaSharp to build by following the instructions, so I'm not sure how to help.

trusktr commented 8 months ago

Does any OS let you synchronously couple window resize to window content drawing? I would think not. Because if that were the case, then any time rendering inside content is delayed, the OS-level window resize experience would become choppy. I highly assume OS devs make this impossible to achieve, without hacks (but I have not verified).

An app limiting OS window animation would be an avenue for DoS attacks.

What I've noticed in macOS is that no matter what the program inside a window is doing, window resize always works on its own with a separate framerate, decoupled from the rendering of the content inside the window. Not 100% sure about Windows except that I know this is true with all web browsers (maybe it was not true with IE at some point, IE was very easy to crash/exploit).

In the above example, I imagine that the rendering is slower than the framerate of the OS window resize, and that solving that is either impossible, or a hack that should not exist.

bmitc commented 8 months ago

Yes, you can synchronously block the resize with GLFW. That's what I used to debug this in the link I posted. I don't know if that blocks all other windows. I mean, once an app is running, it has a lot of ways to mess with the computer, so I don't get the DoS relevance.

The issue I described doesn't occur with GLFW and straight GL calls doing the drawing. It seems to be a SkiaSharp specific issue.

You reference macOS but what platform is that using? It's likely that it is a completely different stack (Cocoa window management with drawing done by Metal), so don't see the relevance. You can have smooth rendering occur during window resizes on Windows. It is just with OpenGL, you need to recreate the surface everytime the window is resized no matter what platform. There's no way around that. This SkiaSharp bug is preventing doing that reliably when using SkiaSharp as the drawing technology.

bmitc commented 4 months ago

@APopatanasov @FrozDark Did either of you ever figure this out? Or find a workaround?

FrozDark commented 4 months ago

@APopatanasov @FrozDark Did either of you ever figure this out? Or find a workaround?

No. It's not necessary for me now.

Mangepange commented 4 months ago

It would be neat if SKXamlCanvas.OnSizeChanged was virtual, just like SKXamlCanvas.OnPaintSurface. In that case it could be overridden and the suggested solution number 2 could be implemented externally :)