Leave a comment

The Mysterious Case of the Invisible Trees

I had this very strange bug that was difficult to repro. All of a sudden, seemingly randomly, all the trees in my world would vanish. It would stay that way for 20 or 30 seconds, and then they would come back.

My first instinct was to debug it with PIX. Some bad value had presumably made its way into the vertex shader and I needed to see what it was. However, whenever I ran the game under PIX, the problem never repro’d.

It happened rarely enough (but often enough that it happened several times a day when debugging my game in VS) that I thought maybe I just didn’t wait long enough. But given that the trees return after several seconds, I couldn’t just leave the game sitting around running under PIX and come back an hour later. I had to stay and watch until the trees vanished – which I certainly wasn’t going to do.

TreeNoTree

Trees. No Trees.

 

Then I finally got a break. I noticed the trees disappeared reliably at one spot in the world at around 90 seconds in to the game, and reappeared at about 120 seconds.

Time to investigate the issue. The relation to game time made sense to me, because my wind vertex shader (used for trees) simulates a sine wave where time is on the x-axis. The calculation, from my understanding, doesn’t have a clear period like a sine wave does. So I need to keep increasing the value of time instead of clamping it to within (-π, π). This means I’ll run into floating point precision errors at some point. But I expected it would be within hours, not seconds.

So I fired up PIX, and unfortunately confirmed my worst fears: it still didn’t repro there, even though I had a solid repro running under VS (and I had also seen it on the Xbox).

My next quick attempt to diagnose it involved hard-coding the Time shader variable to 90 seconds, or whatever value produced the vanishing trees. But doing that, it no longer repro’d anywhere.

My next step, now that I had a solid repro, was to step through the code that set the shader constants. Aha! The wind speed direction value was (NaN, NaN). There was the problem.

Note to self: when objects begin vanishing or looking weird, one of the first things you should do is step through the values you send to the shader and make sure none of them are NaN! I felt stupid for not checking this sooner!

Doing more investigation, I noticed that I already had code that was supposed to guard against this. It was doing a Normalize operation, and verifying that the results were not NaN. Well, it turned out they were actually (-∞, ∞) where I was doing my check, and later turned into NaN in another calculation.

So I fixed my code to check for (Length < Epsilon) before doing the Normalize, and taking appropriate action if so. The bug is fixed.

But there still remained the question of why this didn’t repro when running under PIX, which I didn’t dive into until today.

I undid my fix, and set up the repro again. I ran under PIX, but attached VS to the process at the 90 second mark, and looked at the values that were NaN’d. Well… they weren’t:

Vector2 value = new Vector2(-3.922913E-32f, 2.169361E-28f);
Vector2 direction = Vector2.Normalize(value);
 // direction is now: {X:-0.0001808326 Y:1}

Then I started the process again under VS. This is what I saw:

Vector2 value = new Vector2(-3.922913E-32f, 2.169361E-28f);
Vector2 direction = Vector2.Normalize(value);
 // direction is now: {X:NaN Y:NaN}

The same values produced different results for Vector2.Normalize depending whether the process was started under the debugger or not. This wasn’t a Debug vs Release thing, as both binaries were Debug. And there is obviously no subtle timing weirdness going on.

I fired up .net Reflector and looked at the implementation of Vector2.Normalize:

public static Vector2 Normalize(Vector2 value)
{
    Vector2 vector;
    float num2 = (value.X * value.X) + (value.Y * value.Y);
    float num = 1f / ((float) Math.Sqrt((double) num2));
    vector.X = value.X * num;
    vector.Y = value.Y * num;
    return vector;
}

Pretty straightforward. I suspected the JIT was doing some kind of optimization that changed the result, but it wasn’t obvious what that was. I wrote my own function identical to this one, and it always returned (NaN, NaN), and each operation produced the expected value. Especially the num2 value, which ends up being 0. That kind of screws everything up.

I figured this was because the JIT was optimizing the XNA framework’s implementation more than my own, for whatever reasons.

Then I finally hit on it… what would cause num2 to not be zero? I re-coded my Normalize function to use double everywhere until the final case at the end:

private Vector2 MyNormalizeDouble(Vector2 value)
 {
 Vector2 repro1;
 double num2 = ((double)value.X * (double)value.X) + ((double)value.Y * (double)value.Y); // 0
 double num = 1f / (Math.Sqrt(num2));
 repro1.X = (float)(value.X * num);
 repro1.Y = (float)(value.Y * num);
 return repro1;
 }

And lo and behold, I now got the non-NaN result!

So the lesson is, when dealing with floating point numbers, the optimization made by the JIT can affect the result you get.

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

The Space Quest Historian

Adventure game blogs, Let's Plays, live streams, and more

Harebrained Schemes

Developer's blog for IceFall Games

kosmonaut's blog

3d GFX and more

Halogenica

Turn up the rez!

bitsquid: development blog

Developer's blog for IceFall Games

Game Development by Sean

Developer's blog for IceFall Games

Lost Garden

Developer's blog for IceFall Games

Memories

Developer's blog for IceFall Games

Casey's Blog

Developer's blog for IceFall Games

Blog

Developer's blog for IceFall Games

Rendering Evolution

Developer's blog for IceFall Games

Simon schreibt.

Developer's blog for IceFall Games

Dev & Techno-phage

Do Computers Dream of Electric Developper?

- Woolfe -

Developer's blog for IceFall Games

Ferrara Fabio

Game & Application Developer, 3D Animator, Composer.

Clone of Duty: Stonehenge

First Person Shooter coming soon to the XBOX 360

Low Tide Productions

Games and other artsy stuff...

BadCorporateLogo

Just another WordPress.com site

Sipty's Writing

Take a look inside the mind of a game developer.

Jonas Kyratzes

Writer, game designer, filmmaker.

%d bloggers like this: