Skip to content
This repository has been archived by the owner on Mar 30, 2019. It is now read-only.

[Toolkit WPF] Memory Leaks, Undesired Outcomes and Exceptions #380

Closed
dfkeenan opened this issue May 11, 2014 · 24 comments
Closed

[Toolkit WPF] Memory Leaks, Undesired Outcomes and Exceptions #380

dfkeenan opened this issue May 11, 2014 · 24 comments

Comments

@dfkeenan
Copy link
Contributor

Hi,

I have been playing with some WPF stuff and looking at examples and have been having some issues so I thought I would discuss it here before I change anything.

Memory Leaks:

SharpDXElement Disposes itself when unloaded which is fine. But the Unloaded event is not fired when an application is closed so this causes some leaks. The best fix I could fined was making the SharpDXElement also handle the event "Dispatcher.ShutdownStarted".

The "MiniCube.SwitchContext.WPF.MVVM" demo also has memory leaks because the games are not Disposed. I am not sure if this should be treated as demo/guidance issue or changes should be made to how the WPF integration cleans itself up.

Undesired Outcomes:

WPF elements can be Loaded/Unloaded many times in there life span but because the SharpDXElement disposes itself in the Unloaded event the next time it is "Loaded" it is effectively dead/useless. I propose we change the SharpDXElement so the Direct3D9 setup happens in Loaded and still clean up resources in Unload but perhaps not mark it has "Disposed".

Exceptions:

The call to the DeviceEx constructor in SharpDXElement can throw exceptions (D3DERR_NOTAVAILABLE/NotAvailable) in certain situations. I get it to happen when the SharpDXElement is hosted in an AvalonDock LayoutAnchorable that is collapsed and you hover over the tab to show it. I am not sure what the actual cause this or what (crazy/awesome) stuff AvalonDock does. I am currently trying to see if I can get it to happen without using AvalonDock.

Other stuff:

I noticed that the method "GameWindowDesktopWpf.HandleElementLoaded" uses it's own search mechanism to find the window. Is there any reason you didn't just use the built in method like "window = Window.GetWindow(element);".

In the "MiniCube.SwitchContext.WPF.MVVM" demo the "GameView" class has a custom dependancy property "GameProperty" is there any reason why this couldn't be baked into the SharpDXElement.

Cheers,
dfkeenan

P.S. Sorry kind of long. 😄

@ArtiomCiumac
Copy link
Contributor

The best fix I could fined was making the SharpDXElement also handle the event "Dispatcher.ShutdownStarted

When an app is shut down - all resources are freed by the OS anyway, so I don't see a big problem here. The only applicable scenario - when there are several dispatchers in several threads - but usually this means that the solution is overengineered.

I am not sure if this should be treated as demo/guidance issue or changes should be made to how the WPF integration cleans itself up.

No demos are the unbeatable truth. DirectX is quite an advanced tech and it is expected to developers to think for the architecture themselves and see the issues - like you did.

I propose we change the SharpDXElement so the Direct3D9 setup happens in Loaded and still clean up resources in Unload but perhaps not mark it has "Disposed".

I agree, this could be a good improvement. Also D3D9 runtime can be initialized only once per thread with some sort of reference counting mechanism - i.e. if there are multiple SharpDXElements - it will be initialized only once.

Is there any reason you didn't just use the built in method like "window = Window.GetWindow(element);".

There is no reason, this should be fixed.

In the "MiniCube.SwitchContext.WPF.MVVM" demo the "GameView" class has a custom dependancy property "GameProperty" is there any reason why this couldn't be baked into the SharpDXElement.

This was implemented like this because SharpDXElement is disposable and is single-use only, if improvements you mentioned above will be made - the Game dependency property can be moved to this class.

You did a really good analysis, I will see when I can do all these improvements, however currently my free time is quite limited.

@dfkeenan
Copy link
Contributor Author

When an app is shut down - all resources are freed by the OS anyway, so I don't see a big problem here. The only applicable scenario - when there are several dispatchers in several threads - but
usually this means that the solution is overengineered.

Without handling the event "Dispatcher.ShutdownStarted" I was getting 3 warnings about possible leaks.

@ArtiomCiumac
Copy link
Contributor

I have improved the SharpDXElement in in 5ea239d (in a separate branch for now as it needs more testing), also as samples are now in a separate repository - they will need to be synchronized when we will push updated nuget packages.

Have a look at my changes and let me know what you think about it.

@dfkeenan
Copy link
Contributor Author

Hi @ArtiomCiumac ,

Nice! Looks like you did a fair bit of work there. (I didn't realise it would require so much). Thanks for all the help with this.

I will try to pull this down tonight or tomorrow and test it out.

I still didn't manage to workout why AvalonDock makes DirectX angry. I will try to give this a bit more investigation this weekend as well. I doubt I'll be lucky enough that these changes fixed that as well.

Cheers,
Dan

@dfkeenan
Copy link
Contributor Author

Hi @ArtiomCiumac ,

Doesn't quite work :(

Because the "back buffer" is been cleaned up on Unload but not re-created on Load. I "fixed it" by changing the "GameWindowDesktopWpf.HandleElementLoaded" method to be like:

    private void HandleElementLoaded(object sender, RoutedEventArgs e)
    {
        window = Window.GetWindow(element);
        if(element != null && presenter != null)
        {
            element.SetBackbuffer(presenter.BackBuffer);
        }
        OnActivated(this, EventArgs.Empty);
    }

Not sure if that is the right/best way to do this.

Cheers,
Dan

@dfkeenan
Copy link
Contributor Author

At first I thought these changes had fixed the problem with AvalonDock. It did improve, anchorables could be torn-off and docked without problems. The hiding/showing of anchorables seemed to work most of the time, but I still got it to crash. Seemed to be a timing thing (how quickly I tried to show after hiding). But it also crashes when shutting down. It runs the game loop after the graphics device has been disposed.

Maybe it does some weird threading or child window things. I might have to go spelunking through their code :(

@dfkeenan
Copy link
Contributor Author

Turns out you have to call Game.Exit() before you dispose. Makes sense. That fixes the crash on close. Still working on the Show/Hide problem.

@ArtiomCiumac
Copy link
Contributor

I think disposing a game should safely close it even if Exit was not called, this should be fixed.
Regarding the show/hide - I suppose it may be the fact how AvalonDock handles element rendering and lifetime - if it is possible - please attach a project (or some minimal code) that reproduces the issue.

@dfkeenan
Copy link
Contributor Author

I do have a test project specifically for testing these changes. Just trying to find somewhere online to put it.

Wish you could just attach zips to issues.

@ArtiomCiumac
Copy link
Contributor

@dfkeenan, just upload it to onedrive (former skydrive) or dropbox.

@dfkeenan
Copy link
Contributor Author

Hi @ArtiomCiumac ,

Sorry I took so long to get back. I have put my test app here ge.tt for the time being. I was doing my tests after I made the changes specified above ("GameWindowDesktopWpf.HandleElementLoaded"). It references the SharpDX assemblies using the targets files from the SharpDX repository folder.

Let me know if you have any troubles

Cheers,
Dan

@ArtiomCiumac
Copy link
Contributor

Apologies for such long delay, now I have a little bit more time and checked your demo project.
Surprisingly, it seem to work fine with the latest build of the wpf branch, so I decided to merge it into master_integration. Please have a look at it and try on your machine.
Be aware that there are delays when manipulating the tabs as SharpDX internally needs to recreate the render targets.

@dfkeenan
Copy link
Contributor Author

Hi @ArtiomCiumac, no need for apologies. I will do a pull and try it out soon.

@dfkeenan
Copy link
Contributor Author

Hi @ArtiomCiumac ,

I can still get it to crash unfortunately. If you unpin the tabs so they collapse to one side and you resize one then switch back and forth between active tabs it crashes. I made a problem step recording of it to help try and demonstrate http://ge.tt/9kT3eyn1/v/0?c

Cheers,
dfkeenan

@dfkeenan
Copy link
Contributor Author

Hi @ArtiomCiumac ,

I think I might have found another problem with the WPF platform. :( It appears that if you load textures (via the content manager at least) you get the "Warning: Live ComObject [0xXXXXXXX], potential memory leak" message when closing down.

Cheers,
dfkeenan

@ArtiomCiumac
Copy link
Contributor

Right, I reproduced the issue and, well, we are trying to combine the uncombinable here.
As AvalonDock recreates the UI elements quite frequently - at some point D3D runtime cannot keep up to recreate the DeviceEx fast enough and it crashes (most probably it's a syncronisation issue).

The only feasible solution would be to decouple completely D3D9 management from the SharpDXElement lifetime, but developer will need to either call manually something like D3D.Start(); and D3D.Stop(); or we will need to bind this not to the underlying WPF element Loaded/Unloaded events but to the entire WPF app lifecycle. This is quite a lot of work and needs to be evaluated carefully, for me it feel like beyond the scope of Toolkit. What do you think?

@dfkeenan
Copy link
Contributor Author

Hi @ArtiomCiumac ,

You are right, moving D3D9 management out of the SharpDXElement might be the best option. My proposed solution would be to:

  • Move the D3D9 managment into the GameWindowDesktopWpf class;
  • Refector a fair bit of the code in SharpDXElement into a new class D3D11Image (that inherits from D3DImage class)
  • Change the "SetBackBuffer" method of SharpDXElement so it takes a parameter of the new D3D11Image class.

With this design the lifetime of the D3D11Image instance could be managed by the game class as well. This way the game class could be resposible for all D3D management and the SharpDXElement could be reduced to simply have the resposibility of rendering the D3D11Image to screen, resize notification etc.

If this sounds like an ok plan I could probably try to put something together over the next few days (or on the next weekend).

Cheers,
dfkeenan

@ArtiomCiumac
Copy link
Contributor

Not sure if it is correct to let GameWindowDesktopWpf class handle the D3D9 runtime, as it has to be a singleton - one single D3D9 Device is enough for the entire application, otherwise it may waste additional resources if multiple games are used.

@dfkeenan
Copy link
Contributor Author

@ArtiomCiumac , By moving the D3D9 stuff into the GameWindowDesktopWpf, I meant just move the "private static readonly ThreadLocal<RefCounter> d3d9 = new ThreadLocal<RefCounter>(() => new RefCounter());" and realated classes.

@ArtiomCiumac
Copy link
Contributor

ok, I got the idea - let's give it a try.

2014-07-15 9:00 GMT+01:00 dfkeenan notifications@github.com:

@ArtiomCiumac https://github.com/ArtiomCiumac , By moving the D3D9
stuff into the GameWindowDesktopWpf, I meant just move the "private static
readonly ThreadLocal> d3d9 = new ThreadLocal>(() => new RefCounter());" and
realated classes.


Reply to this email directly or view it on GitHub
#380 (comment).

@dfkeenan
Copy link
Contributor Author

Alright, I'll try to make a start soon.

@ArtiomCiumac
Copy link
Contributor

@dfkeenan, please let me know if this issue is fixed and can be closed as the pull request above has been merged.

@dfkeenan
Copy link
Contributor Author

Hi @ArtiomCiumac ,

I think the work we have done has solved the problems. I am happy for you to close this issue. (I can always raise another one or reopen this one if I find more problems later :D ).

Thanks for all your help.

Cheers,
dfkeenan

@ArtiomCiumac
Copy link
Contributor

ok, I'm closing this issue.
Thank you for contribution!

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

No branches or pull requests

2 participants