excubo-ag / Blazor.Canvas

https://excubo-ag.github.io/Blazor.Canvas/
MIT License
217 stars 23 forks source link

Poor Performance #140

Closed Timmoth closed 3 years ago

Timmoth commented 3 years ago

Hi Stefan, I'm writing a small graphics library using Blazor & HTML5 Canvas.

I've chosen your Blazor.Canvas library as the wrapper around the Canvas API, It works really nicely when there are relatively few draw calls to the Canvas (<10 items gets 125fps+) but as soon as you start trying to redraw > 30 items per frame the performance drops significantly, reaching ~10fps at ~50+ items.

I have implemented 'Redraw regions' as you can see here which considerably improves performance when there is not much motion by only redrawing items that have been invalidated (or are close enough to a nearby invalidated item). But as you can see by a small (and nieve) physics simulation test application when there is alot of motion the performance is unusable.

Could you offer any insight into how one might improve the performance of a Blazor app using Blazor.Canvas or any of the limitations of the platform that cause the issues.

Thanks, Tim.

stefanloerwald commented 3 years ago

Hi Tim,

It's hard to assess what the reason for this performance issue is from just looking at the code. At its core, there is a lot of js interop happening, which is inherently slow. I can see that you already use batching, which is a good idea. The performance can be even better when operations of the same kind are executed directly after each other.

I noticed that you're measuring the total time for the draw method, so both adding items to the render batch and executing the batch. I think it's worth seeing how much time is spent on adding items, and how much time is spent on the execution. The execution happens on dispose of the batch object.

If execution is the issue, you might benefit from writing dedicated JS to solve exactly your need, trying to minimize the data passed between C# and JS.

Hope this helps Stefan

Timmoth commented 3 years ago

Thanks for the feedback!

The performance can be even better when operations of the same kind are executed directly after each other.

Hmm it might be difficult for me to group operations of the same type together since layering is important but i'll keep that in mind.

I noticed that you're measuring the total time for the draw method, so both adding items to the render batch and executing the batch. I think it's worth seeing how much time is spent on adding items, and how much time is spent on the execution.

Interesting, I'll give this a go and let you know!

If execution is the issue, you might benefit from writing dedicated JS to solve exactly your need, trying to minimize the data passed between C# and JS.

I was thinking this may be the solution, though I would like to keep as much of the library in C# as possible.

I read this article earlier and was wondering if you had any thoughts on using InvokeUnmarshalled to call js? https://www.meziantou.net/optimizing-js-interop-in-a-blazor-webassembly-application.htm

stefanloerwald commented 3 years ago

Hi Tim,

Unmarshalled interop is certainly something that is considered for the future of this library. However, at the moment it is both undocumented and not fully supported. My experiment in making it work failed, so I'm not personally investing more time into this until it is documented and fully supported. Pull requests are of course very welcome, should you be willing to implement it!

BR Stefan

iSeiryu commented 9 months ago

InvokeUnmarshalled is marked as Obsolete now: https://docs.microsoft.com/en-us/dotnet/api/microsoft.jsinterop.webassembly.webassemblyjsruntime.invokeunmarshalled?view=aspnetcore-8.0

Even though they recommended using it here https://github.com/dotnet/aspnetcore/issues/21426#issuecomment-629435812 the new recommendation is JSImportAttribute https://learn.microsoft.com/en-us/aspnet/core/client-side/dotnet-interop?view=aspnetcore-8.0

Btw, I wanted to open another performance related issue because my heavy animation experiment resulted in a pretty glitchy output https://iseiryu.github.io/hexagon Here is the code https://github.com/iSeiryu/iseiryu.github.io/blob/main/BlazorExperiments/Pages/Hexagon.razor.cs

But then I built the same thing with raw Javascript in a barebone html file outside of Blazor and it glitched even more in every browser. So far all tests I did with CPU intensive computations, rendering HTML elements, and rendering animation in a 2D canvas (using your library) show that Blazor WASM with AOT is more performant and in certain cases several times more performant than raw JS.

Having said that there are still opportunities to improve performance of this library (btw, this is the only canvas library for Blazor that is still alive, all others died a while ago). @stefanloerwald Have you looked at JSImportAttribute yet?

stefanloerwald commented 9 months ago

There's no work planned on this.

I looked at your example and it actually performed very well on my devices. Nevertheless, it's likely that you'd be better off using GPU resources by switching to opengl.

iSeiryu commented 9 months ago

I improved performance after I left my previous comment but it still glitches on a mobile Chrome. It works fine on a desktop Chrome but Firefox shows the best performance on both desktop and mobile for some reason - even with 1,000 lines.

How would I use OpenGL with your library?

stefanloerwald commented 9 months ago

This is something that's not supported in this project. See https://developer.mozilla.org/en-US/docs/Web/API/WebGL_API