AvaloniaUI / Avalonia

Develop Desktop, Embedded, Mobile and WebAssembly apps with C# and XAML. The most popular .NET UI client technology
https://avaloniaui.net
MIT License
25.39k stars 2.2k forks source link

Custom drawing operation - access violation #13521

Open JaggerJo opened 10 months ago

JaggerJo commented 10 months ago

Describe the bug

Custom render operation throws access violation exception. Can't figure out if/ what I'm doing wrong.

To Reproduce

Run the attached project and resize the window for a while. Sometimes it fails after a few seconds, sometimes it takes a few minutes. (you need to keep resizing).

Screenshot 2023-11-07 at 11 23 24

At some point the app crashes.

Screenshot 2023-11-07 at 11 15 36

Expected behavior

I'd expect the app to not crash, or an exception I can work with :D

Screenshots

Environment

Additional context

I'm probably doing something wrong in the custom drawing operation. I just have no clue where to look.

Archive.zip

timunie commented 10 months ago

Do you log unhandled excepctions already? https://docs.avaloniaui.net/docs/next/concepts/unhandledexceptions

timunie commented 10 months ago

If I debug your sample, I get Attempted to read or write protected memory. This is often an indication that other memory is corrupt.

So I think due to fast resizing some memory is not yet freed.

JaggerJo commented 10 months ago

If I debug your sample, I get Attempted to read or write protected memory. This is often an indication that other memory is corrupt.

So I think due to fast resizing some memory is not yet freed.

Yeah, that's it.

Do you think the error (memory not yet freed) is in my code or in Avalonia?

Any tips on how to proceed?

Thanks Tim!

timunie commented 10 months ago

Could even be an issue in SkiaSharp. Tbh, I have no clue where to best fix that issue. If I were you, I'd clone the Avalonia source and place the sample you have into src/samples/Sandbox. Then you could try to debug it there and see if the debugger stops anywhere in the Avalonia source.

Also you could try to dispose the objects asap. means instead of using var ... you may want to wrap all into using blocks. See: https://www.damirscorner.com/blog/posts/20200103-BeCarefulWithUsingDeclarationsInCSharp8.html

MrJul commented 10 months ago

To help debugging: add a GC.Collect() call at the end of the custom rendering code to force finalizers to run. Then resize your window: the crash should reproduce immediately. (It's usually in SKImage's finalizer, so it looks like a double free issue.)

With this, you should be able to trim down your code to narrow down the part causing the crash.

JaggerJo commented 10 months ago

@MrJul adding GC.Collect() does not make it fail instantly.

MrJul commented 10 months ago

Sorry, put it right after the if (_acrylicNoiseShader == null) block, like this: image

Then resize, 100% repro here (on Windows though).

I thought at first it was the SKBitmap from the acrylic noise shader being disposed when the shader is drawn, but removing the using doesn't solve the problem. At least you should have a base to debug now :)

JaggerJo commented 10 months ago

@MrJul aha, yeah - also fails immediately when I resize on macOS.

JaggerJo commented 10 months ago
image

@MrJul It really is the bitmap. But even keeping the bitmap around (as a class field, never disposing it) still crashes.

Screenshot 2023-11-09 at 09 24 07
Gillibald commented 10 months ago

Can you try not to dispose it via using pattern? You can also try not the load the bitmap on each render. And just load it in your control once.

JaggerJo commented 10 months ago

@Gillibald loading the SKBitmap only once (in the constructor) and keeping a reference to it in a private field still crashes.

Archive.zip

Gillibald commented 10 months ago

Probably some SkiaSharp bug. Can you try to wrap the stream inside some SKData instance and use that for SKBitmap.Decode?

I can not test this myself at the moment. Try to keep that SKData alive.

JaggerJo commented 10 months ago

@Gillibald still crashes immediately after resizing

image
timunie commented 10 months ago

@JaggerJo I'm sharing this sample as I like the design very much. Maybe someone finds even the solution :-). I hope you don't mind.

JaggerJo commented 10 months ago

@timunie I can't take credit for that, mostly took it from here

workgroupengineering commented 10 months ago

I see that the exception occurs on the following line but I don't understand why.

https://github.com/AvaloniaUI/Avalonia/blob/9cd8dda5206ffbcb6705ada2ae8d3753aaaf9caf/src/Skia/Avalonia.Skia/SurfaceRenderTarget.cs#L151

calee88 commented 10 months ago

I'm getting an access violation error from SkiaApi. I downgraded Avalonia to 11.0.4 and the error has gone. 11.0.5 uses SkiaSharp 2.88.6 and 11.0.4 uses 2.88.3. I guess SkiaSharp 2.88.6 is causing the problems.

timunie commented 10 months ago

@calee88 probably yes. In that case it is a bug in a dependency. Due to a vulnerability we had to upgrade in a short time. See #13109 for reference.

Can you double-check using this PR? #12729

timunie commented 10 months ago

https://github.com/mono/SkiaSharp/issues/2456 seems to be the root cause to me

FoggyFinder commented 1 month ago

any updates? without a fix it's impossible to use ICustomDrawOperation

timunie commented 1 month ago

@FoggyFinder please ask this in the linked dependency issue.

FoggyFinder commented 1 month ago

@FoggyFinder please ask this in the linked dependency issue.

@timunie not confident issue you've mentioned is related. TS wrote - "If i make the method synchronous by taking out the await, then I cannot reproduce the error".

SKBitmap bitmap = ImageMethods.Instance.Create8BitSKBitmapFromSingleArray(singleArray, 0, width * height, colorTable);

await DisplayAlert("Something", "Ask Something", "Yes");

return bitmap.Copy();

So at least there was easy workaround. Pretty sure snippet above can be easily rewritten in thread-safe manner as well.

FoggyFinder commented 1 month ago

Q: how to execute something on render thread ?

        public void Render(ImmediateDrawingContext context)
        {
            var leaseFeature = context.TryGetFeature<ISkiaSharpApiLeaseFeature>();
            if (leaseFeature is not null)
            {
                using var lease = leaseFeature.Lease();
                lease.SkCanvas.Clear(SKColors.Green);
                // Do something on Ui thread with canvas - access violation
                // resize window to speed up crash
                Dispatcher.UIThread.InvokeAsync(() => { lease.SkCanvas.Clear(SKColors.Red); });
            }
        }

so Q is: how to ensure operation is executed on render thread? I tried to add .With(new Win32PlatformOptions { ShouldRenderOnUIThread = true }) but it didn't work