7
\$\begingroup\$

I have been having some minor frame drop issues in a 2D XNA game.

To start with, I am a vsync'ed, mixed timestep. That is:

IsFixedTimestep = false;
graphicsDeviceManager.SynchronizeWithVerticalRetrace = true;

This is combined with a fixed physics timestep of 120hz, implemented with the pattern used in Fix Your Timestep! to avoid jittery physics movement. So, Update runs with a variable timestep, and another Tick runs with a fixed timestep (the idea is from Unity's Update/FixedUpdate).

When a sprite moves across the screen at a steady pace, every couple of seconds a jitter/hiccup is noticable. Some googling around led me to this forum post several years ago talking about the same issue.

To test this, I added this code to an Update method:

if(gameTime.ElapsedGameTime.TotalMilliseconds > 22)
{
    Logger.Log(gameTime.ElapsedGameTime.TotalMilliseconds.ToString());
}

Sure enough, every couple of seconds the log showed a time above 30 milliseconds, usually around 32. Seeing as one frame at 60 fps is 16.6 milliseconds, it seems that a frame is simply being lost. I added some additional code to flash the screen red whenever this happens. Again, sure enough, I notice the bullet hiccuping when the screen flashes red. If it is simply dropping a frame, that means that the bullet will hold still, then when the frame finally runs, the physics will run 4 times (since it runs twice as fast as the monitor, over two frames) and move the bullet a lot.

Now, I'm not sure if I can fix this. If this is indeed a quirk of XNA (and gamedev in general?) the frame drop may be unavoidable. So, I would either know how to either get rid of the frame drop, or mitigate how visible it is. A few more points:

1. It's probably not the GC.

In that forum post, they seemed to hone the problem down to a point where the GC would never/hardly run, and still saw the problem. To check myself, I added some code that checked GC.GetTotalMemory(false) every frame, and if it dropped from the last frame, log "GC". I think a drop in that would show a GC collect, but I could be wrong. "GC" was logged independent of the other log when a frame was dropped.

2. I can't use a variable timestep

I need the physics to be reproducible across a network. This means I need it to be as deterministic as possible (it actually doesn't matter 100%, but it will cause noticable artifacts the less deterministic it is). So, I can't just multiple the velocity of objects by gameTime.ElapsedGameTime.

3. I don't want to use IsFixedTimeStep

I like to keep the framerate under my control a bit. It gives more control over drawing, and allows several somewhat independent framerates. The forum post also stated that setting IsFixedTimeStep to false helped the problem. Granted though, I basically reimplented IsFixedTimeStep myself!

Additionally, setting IsFixedTimeStep = true; doesn't help the stuttering.

4. Some ideas?

So if I can't fix the problem, perhaps I can mitigate it somehow. Some ideas I've had are:

  • Motion blurring
  • Give trails to bullets
  • Increase the amount of "noise" on the screen so that a frame drop is a small issue
  • Separate update and draw in to separate threads?
  • Deal with tearing and unsync from the vertical retrace? This almost completely solves the problem, but GPU usage goes to 100% as it's updating and drawing as fast as it can.

Maybe something has also happened with XNA since the forum post in 2008, that I can use. Otherwise, I'm not sure what to do about the issue. Is it a really small issue? Yeah! I'd love to have a silky-smooth game, though. I'd love any ideas!

\$\endgroup\$
5
  • \$\begingroup\$ I am not an expert, but I believe that since you're running with vsync, and monitors aren't exactly spot on 60Hz, you'll have a leftover frame or be a frame behind at some point, since the monitor Hz doesn't match the game FPS. EDIT: Note that disabling vsync and keeping IsFixedTimeStep will keep GPU from going crazy. \$\endgroup\$
    – William
    Aug 4, 2012 at 16:19
  • \$\begingroup\$ I figured something like that would be the culprit. Unfortunately it does make a visible artifact, and I'm looking for a way to mitigate how noticeable that is. When dealing with physics I can actually use that little leftover time to blend the positions of the objects cleanly. That's a lot harder to do for monitor refreshes though. As for your edit, I would assume that would still cause a hiccup, because the 60hz fixed timestep would be out of phase with the ~60hz monitor refresh. \$\endgroup\$
    – Snea
    Aug 4, 2012 at 16:22
  • \$\begingroup\$ Are you able to reproduce the issue in an empty project? \$\endgroup\$ Aug 5, 2012 at 10:53
  • \$\begingroup\$ Hi @Snea, did you ever find a solution for this issue? Ive battled the same issue on and off trying to mitigate those hiccups but to no avail. In each draw call, detecting how far along in the frame I am divided by the timestep I use to interpolate between the last positions and current positions, this helped smooth it out nicely, but when running fixed at 60fps and a frame is dropped that doesnt help. \$\endgroup\$
    – Joshjje
    Jan 4, 2015 at 17:10
  • \$\begingroup\$ @Joshjje I'm afraid not. It could be possible that MonoGame behaves differently and better, I haven't tried it. \$\endgroup\$
    – Snea
    Jan 7, 2015 at 3:03

2 Answers 2

1
\$\begingroup\$

To start, there's a good post by Shawn Hargreaves on GameTime (more specifically, Fixed and Unfixed timestep) over here. Perhaps it will give you an idea or some things to try.

However, I'm not entirely sure that you can rule out the garbage collector.

For Garbage:

Run your game through CLR Profiler and see what it tells you. If you have access to an Xbox 360, you can use the XNA Remote Performance Monitor to tell you more quickly if there's an issue (although, unlike CLR Profiler, it won't tell you where the issue is). There's a series of blog posts by Shawn about using XNA RPM located here, and here.

Also, your method of checking garbage should work. However, my personal favorite way is something like this:

 WeakReference GarbageTracker = new WeakReference(new object());
 int GarbageCollections = 0;

Then, in your update:

if(!GarbageTracker.IsAlive)// if it's not alive.  If it's been collected, it will be set to false
{
      GarbageTracker = new WeakReference(new object());
      GarbageCollections++;
}

This will tell you when a collection happens, as that WeakReference will be collected with every collection that's run. Then, if it's been collected, we increment our counter and set it reset it.

Generally:

Some frame skips are unavoidable. Sometimes an update or (less often) a draw takes longer than it should, and there's no great way to adapt for that (well, more accurately, no great easy way to adapt for that). Off the top of my head, you might want to check gameTime.IsRunningSlowly for any lag. If XNA doesn't think it's running slowly, it's more than likely a refresh-rate problem.

Hopefully this helps!

\$\endgroup\$
2
  • \$\begingroup\$ I like that way of checking for a garbage collection. Unfortunately it didn't tell me anything more, as it seemed to happen at the same time as my other garbage collection tester, which was still independent of a frame skip. I've also had very little luck getting the CLR profiler to work. It just shows 0 for all the memory information. I'll have to give it another try. IsRunningSlowly is also never set to true, with a fixed or unfixed timestep, so it really probably is a refresh rate problem. Is there really no great way to adapt or mask that when it happens? \$\endgroup\$
    – Snea
    Aug 5, 2012 at 7:35
  • \$\begingroup\$ If you're on Windows 7 (and I think maybe Windows Vista, I'm not sure) getting CLRProfiler working can be a bit of a pain. Specifically, you'll want the 32-bit version, and select "V4 Desktop CLR" once the profiler is running. Also, I ran across this blog post where someone was having the same problem you were (although he didn't solve it either): (codeforcake.com/blog/?p=83) I'll update my question if I can think of anything else. \$\endgroup\$ Aug 6, 2012 at 22:00
0
\$\begingroup\$

On the subject of using DirectX/DXGI/XNA and .NET languages -- if your application adheres to DirectX/.NET patterns and practices, garbage collection will not hurt your frame. Unless your application is doing something silly on another thread or you're creating a large amount of objects in a tight loop without proper disposal, 9/10 times it's an issue with main loop logic (rounding errors is a common one) or with rendering code.

\$\endgroup\$

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .