microsoft / Win2D

Win2D is an easy-to-use Windows Runtime API for immediate mode 2D graphics rendering with GPU acceleration. It is available to C#, C++ and VB developers writing apps for the Windows Universal Platform (UWP). It utilizes the power of Direct2D, and integrates seamlessly with XAML and CoreWindow.
http://microsoft.github.io/Win2D
Other
1.8k stars 284 forks source link

Garbage collector impact on smooth animation #454

Closed p8128n closed 7 years ago

p8128n commented 7 years ago

I have been trying to implement a smooth scroll-able infinite canvas using win2D in a C# UWP app. I have implemented all the pointer move and inertia calculation and it works well. The problem I am having is that the UI thread pauses for significant time (up to 18ms) which causes the smooth animation to stutter and look bad. I have tried using a CanvasControl which calls invalidate() in the draw event until animation is complete. This works well for the most part but whenever the garbage collector kicks in it drops a few frames and looks terrible.

To overcome this I tried using an AnimatedCanvasControl with its dedicated thread and CoreIndependentInputSource hoping that this would not be paused by the garbage collector. Unfortunately the game loops thread also seems to be paused by the garbage collector.

Does anyone know how to get around this and create a smooth animation with Win2D? I thought of maybe using C++ to Implement the scroll-able control as a runtime component. But I don't want to put in all that effort of re-implementing things in C++ if it will suffer from the same pauses whenever the C# parent app garbage collector runs.

Thanks!

shawnhar commented 7 years ago

What platform are you doing this on?

.NET GC can usually run for gen0 without halting anything other than the triggering thread, but there are various operations (such as new memory allocations) that will force other threads to suspend until GC is complete. Collecting higher generations is usually less able to leave other threads running throughout.

I would focus instead of why your GC is taking so long. It's been many years (not since the long ago days when I worked with the primitive .NET Compact Framework GC on Xbox 360) that I've seen this being a significant factor in rendering smoothness.

Is your app creating unusually large amounts of garbage for some reason?

How have you identified that GC is the problem here, as opposed to some other source of periodic glitches?

p8128n commented 7 years ago

It is a universal windows platform app. I am creating new CanvasRenderTargets and destroying them as the user scrolls around as I need to virtualize the scroller or I will run out of memory. These render targets contain the background image. When I disable this, the garbage collector does have a much lower impact. The question is though given that I have to be allocating and de-allocating large amounts of memory while it is animating. Is there a way I can run an "unmanaged" thread which runs the animation and is not paused by the GC. If the entire app was written in C++ I don't think I would have this problem. Maybe it is possible to have a xaml control written in C++ which I can include in C# project and is exempt from GC pause?

shawnhar commented 7 years ago

Creating and destroying render targets on the fly is a very bad idea for performance as this can cause all sorts of complex work for the driver. I recommend pooling and reusing these objects. Well optimized graphics code should create all its device resources at load time, and avoid any per-frame GPU resource create/destroy operations.

This kind of expense has nothing to do with managed GC, though. How did you identify the hitches you are seeing as being caused by GC?

p8128n commented 7 years ago

Unfortunately there is no way but to create and destroy resources on the fly. With an infinitely zoomable and panable canvas (which buffers vector graphics to images because redrawing the CanvasGeometry every draw event is too slow) I simply cannot load every possible position and zoom level beforehand.

In the performance profiler I saw that the garbage collector was taking a significant time (greater than 16.6ms frametime). So I made my win2D draw loop flash up an indicator every time the GC CollectionCount increased from the previous draw event. The flashing indicator synchronizes perfectly with the hitches.

shawnhar commented 7 years ago

Unfortunately there is no way but to create and destroy resources on the fly.

Sure there is - implement a pool that hangs on to rendertargets instead of destroying them, in order to reuse your existing object instances to satisfy future allocation requests. You'll need to redraw the contents of the rendertarget when you reuse one for a different purpose, but there's no need to reallocate the object itself.

It is possible your high GC cost comes from some other source of garbage, but I would start by trying to reduce the dynamic rendertarget allocations - that's a very likely suspect for performance problems.

p8128n commented 7 years ago

I'll see if it makes a difference by holding onto the rendertargets until the animation stops. The trouble I have with re-using the rendertargets is they are rarely the same size. Is it possible to resize an existing render target and avoid the allocation cost?

shawnhar commented 7 years ago

You cannot resize a rendertarget, but you could use just part of one.

Closing as I think this has been answered. Feel free to reactivate if you have further questions.