Monday, March 14, 2005

Managed Direct3D InvalidCallException on Second Device Reset

Sorry for the awkward title on this post, but I've been having a hard time solving a Managed Direct3D mystery, and if I can find or figure out the solution, I want the entry to be obvious in Google, because I've been unsuccessful in locating the answer that way myself.


So it seems that people are still reading my managed Direct3D tutorial, because last week I got three questions about it via email. Two of them, surprisingly, were exactly the same. Apparently, if you compile and run the device recovery tutorial, you'll find that everything works great the first time you come back from hitting ctrl-alt-delete. But if you do it again, your application crashes with a Microsoft.DirectX.Direct3D.InvalidCallException during a call to Device.Present. Turning on the DirectX debug spew shows more details about the error:


2300: Direct3D9: (ERROR) :The following D3DPOOL_DEFAULT surfaces/buffers/textures still exist
2300: Direct3D9: (ERROR) :  D3DRTYPE_VERTEXBUFFER
2300: Direct3D9: (ERROR) :All user created D3DPOOL_DEFAULT surfaces must be freed before Reset can succeed. Reset Fails.
2300: Direct3D9: (ERROR) :Reset failed and Reset/TestCooperativeLevel/Release are the only legal APIs to be called subsequently


There are several weird things about this:



  1. It only happens the second time you lose the device.

  2. It only happens if you lose the device via ctrl-alt-delete; you can alt-tab as many times as you like.

  3. I'm pretty sure I'm freeing the VertexBuffer correctly.

So far, I've discovered that it only happens if you create the VertexBuffer in the default pool. If you change to using Pool.Managed, the problem goes away. This makes sense, and is a reasonable workaround, but sometimes Pool.Managed isn't an option, so I'd like to figure out how to use Pool.Default.


So far here's what I've tried with no luck:



  1. Calling Marshal.Release on the VertexBuffer's underlying raw UnmanagedComPointer.

  2. Calling Device.SetStreamSource(0, null, 0) after every render.

  3. Calling GC.Collect and GC.WaitForPendingFinalizers after Disposing of the VertexBuffer.

I'm rapidly running out of ideas. But I'll keep searching, and when I figure something out (or someone tells me what I'm doing wrong), I'll post a comment here and update the tutorial.

41 comments:

  1. Well, a few things caught my eye.. First (in a 'nitpick' kinda way), the calls to TestCooperativeLevel are using exceptions as a flow control.. Would be better if you were using the 'CheckCooperativeLevel' methods performance wise..



    Anyway, back to the root of the problem. The basic cause of this error is that you're creating n+1 vertex buffers (where n is the number of times the device has been reset), even though you're probably not aware of it.. By default, MDX will automatically dispose *and recreate* vertex and index buffers in the Pool.Default space.. The 'correct' way to handle this scenario is to hook the 'Created' event on the vertex buffer, do the lock/unlock code in that event handler, and get rid of the recreation of the vertex buffer you have. (see the 2nd d3d tutorial that ships with the sdk)



    Or, since you're already 'handling' the situation yourself, you could simply turn off the MDX event handling by putting the code:

    Device.IsUsingEventHandlers = false;

    Somewhere before you create the device. Doing this though, you'll lose the automatic 'reset' when resizing your form..



    Either way will work. Let me know if you need further clarification.

    ReplyDelete
  2. Tom: thanks for stopping by. It's hard to beat the horse's mouth, if you'll pardon the expression. I was looking for my copy your book this morning, but couldn't find it. I guess this is even better. :)



    Yeah, I know the exception is suboptimal. Writing the tutorials were my way of learning MDX, and that was something I figured out later. Never got a chance to go back and fix it. Perhaps I should overhaul that tutorial based on your feedback.



    Anyway, thanks a million, that cleared it right up.

    ReplyDelete
  3. Hi Craig

    Thanks for some great tutorials.

    Can't wait to see your update on device recovery tutorial!!



    I've tried to implement Tom's corrections in your code but with no different result. Other samples that I've found on the web have not been optimal, e.g. using TestCooperativeLevel consequently at every frame. Trying to modify those all give the same result: an invalidCallException when calling Device.Reset.

    (Using the Managed pool instead of the Default pool eliminates the problem, indicating that it is due to improper rescource handling.)



    By now I have tried everything. Giving up. I'm capitulating.



    Regards, Teis

    ReplyDelete
  4. Well, unfortunately I've been pretty busy, so I haven't had time to chase this all the way down. I started to make the conversion, but it's not working for me yet, either. But if Tom says it works that way, I have to believe he's probably right. Sort of hard to argue with his expertise. :)



    I'll try to carve out some time this week to get the code working and the tutorial updated.

    ReplyDelete
  5. I'm working on my own separate project (didn't take a look at these tutorials) and I have the exact same problem. I'm using Pool.Default for my VBs, and was recreating them every time I resetted the device. Everything worked fine the first reset, but the 2nd reset caused a problem.



    Basically, when I'd come back to the program when I alt tabbed (mine causes the error with alt tabbing),

    the CheckCooperationLevel returns a DeviceNotReset. So I call Reset, and everything's beautiful. Then I alt tab back out, then come back in. Again, I get the DeviceNotReset, and try to reset it. When I do, I get an InvalidCallException thrown at me.



    I get this error even if I don't recreate my VBs. Some of my VBs are dynamic, and it appears I can't used the Managed pool on these.



    Hoping someone finds a solution,



    --Vic--

    ReplyDelete
  6. I've worked on the problem, and here's what I've minimized it to. My engine uses both dynamic and static VBs. From playing around, it seems that I can only use the Managed Pool for the static VBs - Dynamic requires the Default Pool.



    So, the 2nd Alt-Tab caused the problem of Reset throwing the InvalidCallException. I found out that when I exit the program after Alt Tabbing only once, I get an error on exit. So it seems that for some reason, the first Alt Tab works, but it throws DX in some unstable state. . . . either Alt Tabbing or exiting the program at that point causes problems. I'd urge you to try the same thing. Alt Tab just once, then exit the app, and see what happens.



    With the inclination that it was my VBs causing the problem, I started removing them out, one by one, until I was left with only one static (Pool.Managed) VB. At that point, I could Alt Tab in and out as much as I wanted, and exits were clean. As soon as I threw one dynamic VB in, I'd get the problem. That has to be it!



    I've tried Disposing and not Disposing the VB, and I get both problems. I'm disposing right before I recreate the VB, in the same call. So my two concerns are whether I need to do anything else besides simply call Dispose, and also if it's ok that I do everything in such proximity. I noticed the tutorials do it in two separate events (finally looked over them).



    So that's where I'm at, running in parallel with you guys, trying to figure this bug out. Hopefully we can help eachother out.



    --Vic--

    ReplyDelete
  7. Have you read Tom's comment above? Since he wrote Managed DirectX, it seems likely that what he's said is true. Which would make the tutorial code completely wrong. I just need to go through and rectify it with his suggestion. If you can't wait for me to get time to do that, perhaps you could do the same on your own.

    ReplyDelete
  8. Yeah, I did read it. He said:



    The 'correct' way to handle this scenario is to hook the 'Created' event on the vertex buffer, do the lock/unlock code in that event handler. . .



    Well, in my test, I'm not even locking or unlocking that particular VB anywhere. I simply added the following line in the constructor of my class (as a test):



    testBuffer = new VertexBuffer(typeof(TransformedColoredTextured), 2048, device, Usage.WriteOnly | Usage.Dynamic, TransformedColoredTextured.Format, Pool.Default);



    I keep a reference to it, so I'm guessing that as I alt tab, then DX should automatically create and destroy this VB,a nd I don't have to touch it ever. However, the simple inclusion of that line causes the same alt-tabbing problem. Commenting it out fixes everything. Do I have to do something special since it's dynamic?



    --Vic--

    ReplyDelete
  9. Also, Tom mentions that . .



    By default, MDX will automatically dispose *and recreate* vertex and index buffers in the Pool.Default space.





    I thought that's what the Pool.Managed space was for. I also tried creating a simple (non-dynamic) VB, and setting the space to Pool.Default. I'd get the error. I changed the pool to Pool.Managed. No error. So it seems as if there's something going on here that I'm missing. . . something extra I have to do for Default.



    --Vic--

    ReplyDelete
  10. For me, alt-tab works flawlessly. However, even after hooking the Created event as suggested, I'm still having the problem after a second ctrl-alt-delete. I'm not sure what I'm doing wrong either...but I'm still researching and will post here when I find an answer.

    ReplyDelete
  11. THANK YOU for posting about this. Knowing that I wasn't alone, I was inspired to dig a little deeper and I *might* (sort of) understand what's going on. (Incidentally, I've been learning Direct3D over the past few weeks using, as it so happens, Craig's tutorials and Tom's book. So, salutations gentlemen!)



    First, I’m wondering if Vic was having problems with Alt-Tab while Craig wasn’t simply because Vic’s app was running full-screen and Craig’s was in a window. Is that the case, guys? In any case, Alt-Tab causes the problem for me only when I’m in full-screen mode.



    Second, the mysterious “only on second device reset” behavior isn’t quite so mysterious. Assuming you’ve registered an “OnVertexBufferCreate” handler, you presumably had to call it yourself the first time. When the device is reset after the first device loss, the Managed D3D run-time is calling your handler for the first time. By adding some tracing code I found that it was calling my handler *twice*. So, that’s when the error actually happens. Direct3D doesn’t tank until the next time you try to reset the device.



    So, I stepped through the disassembly a little and found some rather odd behavior in “VertexBuffer.OnParentReset”, a few frames down from my OnVertexBufferCreate handler. Here’s the code:



    00000000 push ebp // Step 7x???

    00000001 mov ebp,esp // Step 8x

    00000003 sub esp,0Ch

    00000006 push edi

    00000007 push esi

    00000008 push ebx

    00000009 mov esi,ecx

    0000000b mov edi,edx

    0000000d xor ebx,ebx

    0000000f xor ebx,ebx

    00000011 mov ebx,edi

    00000013 cmp dword ptr [esi+3Ch],0

    00000017 jne 0000003C

    00000019 cmp dword ptr [esi+10h],0

    0000001d je 0000003C

    0000001f push dword ptr [esi+10h]

    00000022 push dword ptr [esi+28h]

    00000025 push ebx

    00000026 push dword ptr [esi+34h]

    00000029 push dword ptr [esi+38h]

    0000002c push 0

    0000002e push 1

    00000030 push 0

    00000032 mov ecx,esi

    00000034 xor edx,edx

    00000036 call dword ptr ds:[03BB8D40h] // Call to VertexBuffer.CreateObject

    0000003c nop // After call

    0000003d pop ebx // Step 1x

    0000003e pop esi // Step 2x

    0000003f pop edi // Step 3x

    00000040 mov esp,ebp // Step 4x

    00000042 pop ebp // Step 5x

    00000043 ret 4 // Step 6x



    Once you step out of VertexBuffer.CreateObject, things are fairly normal. However, when you hit “Step Over” for the seventh time, instead of returning into the frame below, for some reason the execution point jumps back to the start of the function. Also, while the debugger behavior was fairly normal up until this point, for some reason you can now no longer use “Set Next Statement” – you just get a rather unhelpful popup saying “Unable to set the next statement to this location”. If you step through the function, you find that the second time it does exit as expected.



    I first encountered this with the Feb. 2005 SDK, but upgrading to the April release doesn’t change anything. Can any of you reproduce this behavior?



    I have no idea why this is happening, but it does seem to explain the crash. The only workaround I’ve found is to, as Tom suggested, set Device.IsUsingEventHandlers to false when I’m creating my VertexBuffers and treat them as volatile resources that must be released and restored manually.

    ReplyDelete
  12. Hello all togehter.

    Three days i tried, and debug and ....nearly got nicotin poisend :)

    Good feeling to know that i´am not an idiot: i stuck with the same problems. This is the first page i found, where are figured out contructive methods to solve the problem....my last chance i found for me, is to dispose an recreate the whole stuff...but i think that doesnt make really sense.

    I have the same problems since the Feb-SDK.

    ReplyDelete
  13. Yeah, right now IsUsingEventHandlers seems to be the only workaround. I'm hoping to be able to work on the problem some more, but have very little time to play with it right now.

    ReplyDelete
  14. I worked on at this problem (brain can´t stand still) and i tried the following :

    I used the Garbagecollection (GC)-Class, to find out whats possible wrong and found some interesting (but don´t really know, if this matter in this case, but have a look : )

    gc.GetGeneration(object) gives me the amount of locations, where a pointer points on a memory...(that, how i understand this function) ...if i create a vertexbuffer, the amount is 0 and if i fill it with the SetData-Command (or with lock/Unlock) the GetGenration-function returns 1.

    If i touch the same buffer in one frame (or just after the creation) twice, the return is 2 ! Hahaa ...my hope rised...

    Now i tried to block the second touch of the buffer (no regard to the incorrect result i got through this, cause my method sometimes need a second touch of the buffer (yet)). Now i tought all is fine and i had a grinn on my face but when i debugged further i was shocked: The programm creates more than one Vertexbuffer (ahmm....really.. ?:) and when i created an filled the second buffer the function returned a "2" on my first vertexbuffer... (i looked thousend times, its another buffer, but created from the same class).



    Now i have "Die Fresse voll" (This is german, dont know how explain that in english :) and i build in some compiler-statement to keep the old stuff and switch temporary all to managed code, which works without any problem (but managed is not really what i want).



    Maybe this is a hint on the meesage, that not all localmem-stuff had been released....maybe the framework (or whatever component) is´nt able to count correctly. I checked the access on the buffer through building it as a property to set breakpoint on any access and debugged it step by step. I found no further reference builded on the buffer.



    Maybe someone else with more knowlegde can tell me if this assuming may correct or on can solve the problem with this info.



    -Keep on debugging-



    MfG. LosBrutalos







    ReplyDelete
  15. I've just spent the last 4 days fixing this (or has it been 3? God, it's been too long), well, since Tom Miller finally gave us a render loop that actually works well. (i.e. I changed my current loop to match his, it works very nice in conjuction with the NativeWindow class btw).



    However, it messed up my Alt+Tab code. Well, it didn't work that great to begin with, as it was too convoluted to even imagine. So, clearly I had to rewrite. And hence the 4 days and here I am telling my life's story...



    Anyway, the only solution I was able to find is to disable the events, this isn't so bad really. The only thing -I- missed was the resize event, but using the NativeWindow class I was able to capture the WM_SIZE message and implement my own working Resize event. I also use this class to capture the screen saver/power management crap as well.



    I wonder if this will be fixed in the June SDK though? It really should be as this appears to be a nasty bug.



    Anyway, if you would like to know more about what I did or want code, just email me (you can get my address from my URL).

    ReplyDelete
  16. Hmm. So what did you try in order to get around it that convinced you this is a bug?

    ReplyDelete
  17. Well the fact that the debug spew shows the objects aren't being released on the 2nd try is a good indicator. If it were my code, that error should and would have come up on the first alt+tab (and, in fact it has due to my not releasing resources when required, but was fixed readily enough). However, on the 2nd Alt+Tab from fullscreen to windowed, it crashes handily with resource not freed errors. Whatever's freeing this within Direct3D is not doing it the 2nd time around for whatever reason, even on a simple test this fails. As for getting around it, I didn't get around it per-se, I merely followed the advice of others and disabled the events (which apparently are a performance penalty too). Apparently the only way to get around this (according to Mr. Miller) is to do things in the create event. But this apparently doesn't work (see comments above), and to me this is not optimal design. I don't wish to use events to fill my vertex buffer with data. Believe me, I have a habit to blame my own code before blaming Microsoft/DirectX and I've found bugs with DX before. I have tried a million things to make this work, including options mentioned here. Nothing has worked except disabling events.

    ReplyDelete
  18. Fair enough. I have to admit that this does look like a MSFT bug.

    ReplyDelete
  19. Out of curiosity, has anyone tried reproducing the bizarre stepping behavior I described in my post above? I'm really curious as I can't figure out what manner of bug could generate this symptom - it seems to me that the DLL containing VertexBuffer.OnParentReset must actually be invalid in some way.

    ReplyDelete
  20. Sorry - I've been absolutely strapped for time. There's a chance I'll be able to spend a lot more time on my DirectX stuff in July, but until then I'm unlikely to get to it. Hopefully someone else will.

    ReplyDelete
  21. I've just heard from Microsoft that they now think this is a bug in MDX. It'll be fixed in some future version of the SDK. Hopefully the next one!

    ReplyDelete
  22. Hi guys,



    This is a refreshing read, I thought I was alone on this one :o(. I have not been using DirectX or VB.Net long so I was assuming it was my problem. I’m using a sprite for rending text and textures and dynamic vertex buffers for lines and polygons, which means I’m stuck using pool.default.



    For me its only on the second reset that it stops running and points me to the device.reset line telling me its an invalid call. I’m yet to try turning of the event handlers as I have only just found this suggestion from Tom on here.



    I was wondering if anyone had heard of this bug being fixed yet? Or if there is proven work around for the moment, as some seemed to be struggling with the one Tom suggested also.



    Cheers



    Mat

    ReplyDelete
  23. I did finally get an answer from the MDX team on this, but have been lame enough not to post it. Here's what they said. DMXUT is the class framework they use in the tutorials, I believe. I can't remember what it stands for.

    =======================

    What you are observing is a "by design" issue; hence the code is doing exactly what it is supposed to. According to Tom, "The vertex buffer is being created multiple times on each reset (once by the MDX framework, once by the app itself). However, only one of them is being disposed correctly (the one MDX knows about) while the other is not. Turning off the event hooking mechanism will stop this error from occurring, or by switching to the VertexBuffer created event method to fill the data."



    Tom's broader recommendation is to use DXMUT to do device initialization, since it does the "right thing" and (theoretically at least :-) it gets updated with an SDK revision if we have newer guidance.



    You can either:

    A) use DXMUT (recommended)

    B) quit using the event model and handle object lifetime yourself.

    C) quit recreating the vertex buffer every lost/reset loop and instead use the Created event to 'refill' the vertex buffer's data on reset.



    A is (for obvious reasons) recommended, but C is shown in the tutorials.



    ReplyDelete
  24. Thanks for that response Craig.



    I have had a good look at the DXMUT code suggested but im not sure if I can use it in its current state for my purpose. I have created a DX Control in VB.NET to be used on VB.NET forms. So I do not want it to create a window for me or apply my device to a window.



    I am thinking the only way around this is to try and alter the code to work with my Control rather than a window. The downside is I will lose the added benifit of the DXMUT being updated as you mention.



    Does anyone have a better suggestion on something that I may have over looked?

    ReplyDelete
  25. Did you try option B or C? Neither of those involve DXMUT.

    ReplyDelete
  26. Hello again,



    Sorry for the delay on this one but I was stuck testing most of last week. I had tried all three of the approaches you gave with no success really the best I could get was the same as you with it crashing on the second reset from using the tutorial. I could not get the DXMUT to work with my code and then turning off the event handlers did not work either, it prevented the crash but stopped the control from actually working.



    Anyway I decided to have another look through it all on Friday afternoon and found the same again until I tried swapping over from the TestCooperativeLevel() to CheckCooperativeLevel() as Tom had suggested in his first post.



    E.g.

    If _device.CheckCooperativeLevel = True Then

    _device.Present()

    Else

    DeviceLost = True

    End If





    By using this instead along with the approach you show in the tutorials(C), I seem to have solved the problem and it is recovering every time fine just minus the existing data which is to be expected.



    So if you or others are still having problems getting it to work then I would advise using the tutorial approach but using CheckCooperativeLevel() instead.



    Thanks for the help on this on Craig, I hope this information is of use to you.



    Mathew

    ReplyDelete
  27. Hello again,



    All seems to be going well since i swapped to using the device.CheckCooperativeLevel() instead of TestCooperativeLevel(). Since then I have removed the DeviceLost boolean completely and used another check in its place in render to trigger recovery as i was still getting into the render loop.



    I have however encountered a problem with disposing my vertexbuffers. I have been using VB.dispose() as you suggest in your tutorials but now I am getting past the second reset I have found that after the 20th or so Reset I am running out of video memory. So obviously the .dispose() method is not actually freeing up the space as expected on the GFX card.



    Following a good search of the API/web, and having tried setting them to nothing, I have not been able to prevent this gfx memory leak.



    Any ideas on this one?



    Regards,



    Mathew

    ReplyDelete
  28. Well, not really, no. This seems fairly consistent with what Tom and company describe as the problem, and what I take to be the horribly non-intuitive nature of MDX memory management.



    I suspect the answer (short of going to MDXUT, which I don't like as an answer) is going to revolve around turning off the event handlers. It's there that I suspect MDX is allocating a new VB on your behalf.

    ReplyDelete
  29. How wonderful, but I believe you might be correct on that one as it does make sense based on what Tom and company have said. I assumed it was my mistake as I tend to blame myself before I moan about the API. I will see what I can do with turning them off and report back any findings.



    Regards,



    Mat

    ReplyDelete
  30. Hi again,



    Cheers for that Craig you were spot on with that diagnoses. MDX was recreating my VertexBuffers and I was as well in the SetUpDevice() method as per tutorial. Rather than turning off EventHandling I have opted to just move my VertexBuffer creation to the InitializeGraphics() method instead, which prevents it being called more than once.



    A quick recap for those looking for a solution like I was:



    Using Craig's Device Recovery Tutorial:



    1) Replace calls to device.TestCooperativeLevel() with device.CheckCooperativeLevel().



    2) Move the initialising of VertexBuffers from SetUpDevice() to InitailizeGraphics() as this will prevent u recreating the buffers each time as MDX will do it for you (helpfully?!).



    3) I found I had to replace the DeviceLost Boolean and comparisons with checks to the device.CheckCooperativeLevel() at the same places - this stopped a BSOD I was having due to it still entering the render method.



    Hopefully when you come round to updating your tutorials you will find the same fixes as me solve your problem.



    Many thanks for your help on this one Craig.



    Happy Coding!



    Mat

    ReplyDelete
  31. Fantasic - so glad you figured this out. Glad I could help.



    I do hope to update the tutorials some day - this will be invaluable when I do.

    ReplyDelete
  32. Wow.. i solved it too ...

    Fortunaly i kept the link to this page :)

    "CheckCooperativeLevel" is the way !

    btw I found another problem too:

    One time a sized a vertexbuffer too small. i figured it out only when i had switched on all debug options. it was funny, when i had the wrong size i got a wrong error... until i found the error, then i got the real fault at that point: my vertexdeclaration was not setted at one point.



    Thank you Craig !



    * Los Brutalos *

    ReplyDelete
  33. Glad we could help, but I don't think I deserve much of the credit on this one...thank MrWebber.

    ReplyDelete
  34. I was much happy, that it will work now, that i lost the focus, who´s written the solution...

    Thank you Mr. Webber :)









    ReplyDelete
  35. What a fragile component DirectX is. If the least itty bitty thing goes out of sequence, the next thing it tells you is simply "Error in the Application."



    I cannot get satisfactory device reset behavior with "Device.IsUsingEventHandlers = true" no matter what I do.



    For one, the Created event on the Vertex and Index buffers never fires. Never. At least not for Pool.Default buffers. It would be kind of handy if it did fire, from an encapsulation perspective. But it doesn't. If I do not clean up these vertex buffers outside this event, the renderer inevitably chokes on them the next frame.



    MDX sure must be doing SOMETHING, though, because if I manually release and re-create the buffers on a device reset, the application leaks memory like a seive on each device reset. After enough resets, the application bogs down terribly. I'm not convinced the problem is entirely due to unreleased buffers, either. That would cause the application to leak and degrade at an O(n) rate on each reset, for example, eating 500KB each reset. My observation is that it degrades at a O(n^2) rate on each reset. For example, the first device reset eats 500KB, the next eats an additional 1MB, the next an additional 2MB, etc.



    In my situation, manually managing the release and re-creation of resources, plus setting "Device.IsUsingEventHandlers = false" is the only way I've found around all of these issues. All my resources are safely re-created, and no memory leaks occur. Now I can hit Ctrl-Alt-Del, then Esc like a kid jumping on a trampoline and my application does not flinch.

    ReplyDelete
  36. It *is* somewhat fragile. This is the price one pays for insane levels of performance, I guess.



    I'm not sure what to tell you - I had to stop working on this problem.

    ReplyDelete
  37. Sorry about the vagueness of this comment but I've just had a problem akin to this one and I ended up implementing a successful work-around, so I thought I'd post something here in case it was useful to future vistors. The barrier to my second device reset was a couple of vertex buffers that I was creating in one of my classes. I was using the VertexBuffer.Created events to automatically restore these when the device was reset and I was disposing them in the owning class's Dispose() method. After much head-scratching, I noticed that during the first device reset, the VertexBuffer.Created handlers were being called AFTER the owning class had been disposed. After much further head-scratching, my solution was to check whether the owning object had been disposed in my VertexBuffer.Created handlers and, if it had, Dispose the vertex buffer that was passed to the handler in the sender argument (see code snippet below). My theory is that ManagedD3D helpfully re-creates these buffers even if no Created handler has been supplied, which might explain the orignal problem.



    private void OnBufferCreated(object sender, EventArgs e)

    {

    VertexBuffer buffer=(VertexBuffer)sender;

    if(this.m_bIsDisposing)

    {

    buffer.Dispose();

    }

    else

    {

    // initialise buffer here

    }

    }

    ReplyDelete
  38. Excellent - thanks for following up. Like I said, I haven't been able to work on this problem any more, but I hope that your approach will help others.

    ReplyDelete
  39. I have been reading and re-reading this thread for a few days now. Quite entertaining in a sadistic manner.

    <br>

    I suffer from the same problem but with slightly different behaviour (inconsistent crashes). Let me first describe how I am "handling" device loss:

    <br>

    => I use:

    <br> if(!device.CheckCooperativeLevel())

    statements to determine if I have lost the device (before entering routines that use the device)

    <br>

    => I use a try-catch statement about my devie.present() call to catch a DeviceLostException.

    <br>

    => I have hooked the VertexBuffer.Created and VertexBuffer.Disposing methods (in addition to device lost, etc).

    <br>

    => I do not new or dispose of my vertex/index buffers. Instead, I re-fill my buffers with the appropriate data from within the vertexbuffer.created events

    <br>

    => Finally, and most important, I output lines to the console whenever I get a VertexBuffer.Created or VertexBuffer.Disposing event.

    <br>

    My results are extremely interesting (again, in a sadistic manner):

    <br>

    SOMETIMES, it does not fail. In cases that do not fail, the number of Created events is 1 and is matched by exactly 1 Disposing event.

    <br>

    BUT WHEN IT DOES FAIL, my log indicates that my VertexBuffer.Created event is getting called a minimum of twice. Here on my screen now, I have record of VertexBuffer.Created being called 4 times!

    <br>

    So is this a bug or not? I am working on a project that requires a solution to this problem. What do I do?

    ReplyDelete
  40. Have you considered allocating all your device resources in the Managed pool, rather than Dynamic? That way MDX will re-fill your buffers for you and stop confusing the issue by firing events at you under badly-documented circumstances. After my earlier experiments (see above) that was my final approach and it seems to be rock-solid.

    ReplyDelete
  41. Emailed me by a clever reader:



    I've been fighting with this problem with my MD3D app. In my case, a windowed app was crashing after a screensaver was cancelled. After some experimentation I realised it was associated with switching window stations (what happens when you press Ctrl+Alt+Del or run a screensaver).



    I was pleased to uncover your blog however I wasn't satisfied with the workarounds nor MS's assertion that the behaviour is by design. After some poking around with .NET reflector I'm confident that it's a bug in the MD3D layer.



    The bug centres on how the VertexBuffer objects are recreated when the device is lost. Here's some code from the VertexBuffer object.



    private void OnParentLost(object sender, EventArgs e) {

    if (m_Pool == Pool.Default)

    {

    Dispose();

    if ((sender != null) && Device.IsUsingEventHandlers)

    {

    sender.DeviceLost += new EventHandler(OnParentLost);

    sender.DeviceReset += new EventHandler(OnParentReset);

    sender.Disposing += new EventHandler(OnParentDisposed);

    }

    }

    }



    public override unsafe void Dispose()

    {

    if (this != null && !m_bDisposed)

    {

    raise_Disposing(this, EventArgs.Empty);

    m_bDisposed = true;

    if (parentDevice != null&& Device.IsUsingEventHandlers)

    {

    parentDevice.DeviceLost -= new EventHandler(OnParentLost);

    parentDevice.DeviceReset -= new EventHandler(OnParentReset);

    parentDevice.Disposing -= new EventHandler(OnParentDisposed);

    }



    if (m_lpUM != null && m_lpUM.Release() == 0)

    m_lpUM = null;



    GC.SuppressFinalize(this);

    }

    }





    OnParentLost() is called when the device raises a lost event. This cleans up the unmanaged interface pointer (m_lpUM), tears down the event handlers and resubscribes to the event notifications. This is fine apart from that it doesn't guard against being called more than once before the device has been reset. This is exactly what happens with the standard example code. If Device.Present() fails because of a D3DERR_DEVICELOST error, it raises the device lost event. Later on, when Device.Reset() is called, it raises the event again regardless of what's gone on before. The problem is that VertexBuffer.OnParentLost() doesn't unsubscribe the events on the second call (because m_Disposed is false) but does subscribe to the events again. This means that it will receive two notifications on each event. When VertexBuffer handles the device reset event, it recreates the unmanaged vertex buffer. There's no state management and the vertex buffer is created twice. Member variables are over written and it loses track of one of the instances. Double the amount of VB memory is allocated but this doesn't stop things working immediately. It's when the device is lost for the second time that things go wrong. The framework has lost track of some unmanaged VertexBuffer objects so doesn't know it needs to release them. However we can't call Device.Reset() until these interfaces have been released. Consequently we see "All user created D3DPOOL_DEFAULT surfaces must be freed before Reset can succeed"

    and the call fails with a D3DERR_INVALIDCALL error.



    The way to avoid the problem is to prevent the faulty code being executed. Checking the cooperation level before calling Device.Present() prevents the call raising a device lost event. Not using event handlers prevents the vertex buffer being created twice.

    ReplyDelete