Understanding the Performance Implications of the Event Model

There's a similar scenario that is specific to Managed Direct3D. You've already learned that each resource that is created hooks the device events for at least disposing, and in many cases, device lost and device reset as well. Each of these event hooks can do a few things that can be disastrous to your performance if you aren't aware of them.

First, there is the actual memory allocation. Every time you create a vertex buffer, for example, aside from the normal vertex buffer allocations, just to cover the event handlers, there are three extra allocations of delegates. Couple this with the fact that these event handlers act as hard links to the object itself, and you can see that these objects will never be destroyed until the device itself is. Look at this piece of sample code:

 public void WasteVideoMemory() {     VertexBuffer waste = new VertexBuffer(device, 5000,         0, VertexFormats.None, Pool.Default); } 

It looks like a piece of dead code. Sure, a vertex buffer is created, but it's never used, so obviously the garbage collector will kick in and collect it. This is what you might expect anyway; however, nothing could be further from the truth.

In reality, even this simple piece of code does quite a bit outside of just creating a vertex buffer. After the vertex buffer has been created, the DeviceLost, DeviceReset, and Disposing events from the device are all hooked. These event handlers will maintain a link between the device and the vertex buffer. Even though this vertex buffer is never used anywhere, the garbage collector will never try to collect it because the device still maintains a reference to the object via the event handler.

If a method similar to the preceding one was called numerous times, you would slowly erode away the amount of free video memory you have (since the object was created in the default memory pool). Since you never maintained a reference yourself to this vertex buffer, once it has lost scope, you can never remove it. In a short time you would run out of memory, and be puzzling to figure out why.

Another side effect of this behavior is shutdown time. There have been a few people who have reported "bugs" in the Managed Runtime due to a misunderstanding between how they thought things should work and the reality of what really goes on. It's normally the same story, "I'm doing such and such, and my application locks up when I exit." The scenario is invariably something like this:

 device.SetRenderTarget(0, mySwapChain.GetBackBuffer(0, BackBufferType.Mono)); // Render some stuff 

This code does pretty much exactly the same thing the previous code did. It creates a "new" object (in this case, a surface) that is never used again. Running this line of code thousands of times creates thousands of surfaces that are orphaned and only accessible from the device. When the device is finally disposed on application shutdown, the device signals to each of its dependencies (these thousands of surfaces) that they need to dispose themselves now as well. This "event unwinding" takes a period of time; the more orphaned objects, the longer it takes. Most times, the customer's application hasn't locked up; it's simply trying to handle the events.

There are two ways around this type of behavior. The first one is the easiest to implement. You can simply use the using statement on objects you expect to have a short lifetime. The preceding statement should be

 using(Surface backBuffer = mySwapChain.GetBackBuffer(0, BackBufferType.Mono)) {     device.SetRenderTarget(0, backBuffer);     // Render some stuff. } 

Making sure you eliminate the shortly used objects and replace them with using statements as shown previously can drastically improve the performance of your application if you do a lot of the small allocations.

DISSECTING THE USING STATEMENT

If you are unaware of what the using statement does, it will automatically dispose of the object created in the initialization clause. The C# compiler breaks the above statement into code like this:

 Surface backBuffer; try {     backBuffer = mySwapChain.GetBackBuffer(0, BackBufferType.Mono);     device.SetRenderTarget(0, backBuffer);     // Render some stuff } finally {     if (backBuffer != null)         backBuffer.Dispose(); } 

This doesn't solve the problem of the event hooks, though. Each object will still be created with the device hooks, and the extra allocations for the event handlers could still be detrimental to your application's performance. The events and handlers are in Managed DirectX to allow you, as the developer, to not worry about object scopes and lifetimes. Depending on the needs of your application, though, it may be helpful for you to maintain the object lifetimes yourself.

In the DirectX 9.0 release of Managed DirectX, you were stuck with this behavior, even if you didn't want or need it. With the SDK Update, you now have the choice of turning the event handling in Managed DirectX completely off. See the following code:

 Device.IsUsingEventHandlers = false; device = new Device(...); 

UNDERSTANDING THE EVENT HANDLING

Using this property will only turn off the automatic hooking of events. All events that Direct3D fires will still be fired, so you can hook them yourself. You must use caution when disabling the automatic event handling. You will have much more control over your application, but that extra control will require you to ensure that you manage your object lifetimes correctly.

The default value for this property is true, and it can be changed at any time. Switching it to false before you create the device and never touching it again will turn off all event handling in your application.

It's entirely possible to use the event handling routines for the majority of your application, but turn them off in certain cases. For example, this code is perfectly valid:

 // Device is currently using event handlers Device.IsUsingEventHandlers = false; using(Surface backBuffer = mySwapChain.GetBackBuffer(0, BackBufferType.Mono)) {     device.SetRenderTarget(0, backBuffer);     // Render some stuff. } // Allow device events to be hooked again Device.IsUsingEventHandlers = true; 

FORCING YOUR APPLICATION TO USE A SINGLE THREADING DEVICE

With the ability to turn off the event hooking by resources, the Managed DirectX runtime was updated to always turn on the Multithreaded flag on device creation. If you know you will be handling all of the object lifetimes manually on a single thread, you can turn this feature off as well. Update the following flag in the presentation parameters structure that you use to create your device:

 presentParams.ForceNoMultiThreadedFlag = true 

This code would turn off the event handler hooks while this temporary surface is created, but turn them back on afterward. Depending on your needs, you may want to turn the event hooking off completely or just partially. You will always get better performance handling the object lifetimes yourself than letting the system handle it.



Managed DirectX 9 Graphics and Game Programming, Kick Start
Managed DirectX 9 Kick Start: Graphics and Game Programming
ISBN: B003D7JUW6
EAN: N/A
Year: 2002
Pages: 180
Authors: Tom Miller

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net