Baffled by Device.TestCooperativeLevel().

wyrd

Senior Contributor
Joined
Aug 23, 2002
Messages
1,405
Location
California
I can't seem to get the performance I want out of this thing, and it's baffling me to no end.

Here's the breakdown; Running in full screen, drawing the same sprites 5,000 times on to the screen, at 20 fps.

This is fine and dandy, at least until I minimize and then remaximize the screen. When I try to maximize the game after it's been minimized, it takes about 5-10 seconds for the graphics etc. to re-appear on the screen. This is unacceptable.

I've tried several things before coming to my current implementation. The first one was just auto-checking TestCooperativeLevel before drawing each image. This was fine, but shot my game down to about 5 fps. Ew.

After this, I simply put the recovery routine (RestoreAllSurfaces()) inside a try/catch block. However, the problem I described above cropped up here - but instead of 5-10 seconds, it was more like 20.

The next implementation I tried was having a boolean value (called IsLost) that floated around, and was checked during every draw routine (when SurfaceLostException was caught, it'd set the value IsLost to true to prevent other drawing routines from drawing). Then when the main Display routine was called, it'd check the boolean value and test to see if surfaces needed to be restored. Unfortunately, this was a somewhat ugly "hack" and also gave me problems when a single image was lost in the middle of a frame, giving it a "skipped" look. Not acceptable.

The second to last implementation I tried was handling the Surface.IsLost properties individually. Unfortunately this property seems to be buggy, and I had several issues getting it to work right. Even if it did, I'd assume that the speed of the IsLost property would be about the same as the RestoreAllSurfaces() method.

The final implenetation, which so far seems to be the best, is having the client check to make sure the game surface can be drawn to before actually trying to draw on it. It does this by accessing the game surfaces CanDraw property;

C#:
		/// <summary>
		/// Gets whether this DDGameSurface can be drawn on.
		/// </summary>
		public bool CanDraw 
		{
			get {
				return _device.TestCooperativeLevel();
			}
		}

So far so good, right? I mean if you check this once per frame, that's a heck of a lot better then per sprite (5,000 of 'em). This would also allow me to put the error checking inside a single routine, which is the Display() method of my game surface. Upon finding an error, it tried to recover all surfaces;

C#:
		/// <summary>
		/// Display the current DDGameSurface on to the screen.
		/// </summary>
		public void Display() 
		{
			// Rectangle for surface and buffer is identical.
			Rectangle rect = Rectangle.Empty;
			rect.Width = _surface.SurfaceDescription.Width;
			rect.Height = _surface.SurfaceDescription.Height;

			// Draw buffer to screen.
			try {
				_surface.Draw(rect, _buffer, rect, DrawFlags.Wait);
			}
			catch {
				// Surface was lost.
				_restoreSurfaces();
			}
		}

This would leave the client code looking something like this;

C#:
// Inside process graphics routine.
if (_gameSurface.CanDraw) {
   // Draw sprites.
}

// Inside game loop.
_processSound;
_processGraphics();
// ...
_gameSurface.Display();

Maybe I'm totally missing something, but based off of this there should only be a single check to TestCooperativeLevel(). Given that, there shouldn't be any problem. Right? But there is. Maximizing a minimized game takes 5-10 seconds to redisplay the graphics or even repond to keyboard input.

If anyone can offer me some help, I'd appreciate it.
 
Use TestCooperativeLevel once per loop. The time it takes to reload is not uncommon (Try Alt+Tabbing any commercial DDraw game, it takes 5-10 seconds before the screen restores)
 
I am currently testing it only once per loop. :(

I don't recall commercial DD games doing this, but then again that was so long ago. Oh well. I guess I'll stick with window mode for DD games and move to fullscreen when I start using D3D.
 
RedAlert 2, RedAlert 1, Stronghold and Stronghold Crusader are some of the 2D commercial games I have. They all have a delay before reappearing after an alt-tab seeing as the video memory has been flushed and needs replacing.

Direct3D does this too because one of the main things is you need to explicity reload all objects declared Pool.Default because the device refuses to be reset until they're removed manually using Dispose.

The delay will be there until you get a very fast computer that can reload large numbers of graphics extremely quickly.
 
Well.. what you describe is different then what I'm experiencing. There's only 3 surfaces loaded in my test program, and thus only 3 need to be reloaded. There should be no reason for this to happen in my case. :(

And I have never experienced this type of things in any commercial Direct3D game.
 
Problem solved!

I guess my logic failed me the last few days, as this should of been quite obvious earlier. In my drawing routines (Draw(Graphic) or DrawTransparent(Graphic)) it simply ignores errors, it doesn't try to restore the surface. The only time I tried to restore the surface was during the Display routine. One would think that this would of been enough, specially if I did a check to CanDraw before calling drawing routines. However, what I didn't realize was that CanDraw could succeed without the surface being restored from Display. Woops. :eek: That's what caused the problem, as throwing errors (ie; SurfaceLostException) can cause a massive performance hit. And since I was drawing 3,000 times, it was throwing 3,000 errors.

In a quirky way, it was my own fault, not the fault of DirectDraw. And the code was logically correct, except I just needed to add surface restoring to all my drawing routines, not just the Display routine.

Oy, ever had that "you're an idiot" feeling? I guess this is what happens when you try to study for 2 midterms and program at the same time.
 
Back
Top