3
\$\begingroup\$

I've been trying to set up fixed-timestep loop for my game, using the technique from here. I have no need for the interpolation step as I intend to run the logic at or above vsync speed.

Here is a simplified version of my test code (C#):

const double MILLISECONDS = 1000.0;
const double TARGET_SPEED = 60.0; //Logic tick rate in hertz

const double dt = MILLISECONDS / TARGET_SPEED;

double currentTime;
double accumulator = 0.0;
Stopwatch logicTimer = new Stopwatch();

double theta = 0;
float spinSpeed = 0.005f;

//Game loop:
while (true)
{
    double newTime = logicTimer.ElapsedMilliseconds;
    double frameTime = newTime - currentTime;

    currentTime = newTime;
    accumulator += frameTime;

    while (accumulator >= dt)
    {
        theta = (theta + spinSpeed * dt) % (Math.PI * 2.0);
        accumulator -= dt;
    }


    //Draw sprite, rotated theta radians
    Draw();
}

With TARGET_SPEED at 60Hz on a 60Hz monitor, most of the time it runs smoothly. However, often when I run the test I see periodic jumps in the rotation. The time to complete a revolution does not change, but it appears to stand still for an extra frame or two and then jump ahead to make up the difference. The frequency of these stutters is constant during a run, but subsequent runs with the same settings will stutter at different rates.

I've measured how much theta changes each logic step, and it's rock steady across multiple runs regardless of stuttering.

This leads me to believe the problem is something to do with differences between my game's clock and my monitor's refresh clock. However, I can't figure out anything beyond that.

The problem still happens whether vsync is on or off. It happens less often when TARGET_SPEED is a multiple of 60; for any other value it happens on every run. Increasing TARGET_SPEED past 1000 or 10,000 Hz appears much smoother, but there's no way I can run a real game that fast. Drawing more or fewer sprites to control the time spent rendering doesn't seem to have any consistent effect on the problem.

So basically I'm completely stumped. Any idea why this is happening and how I can fix it?

Here's the full source file for my test project (XNA) if you want to try it for yourself.

\$\endgroup\$
4
  • \$\begingroup\$ Have you profiled the code? I appreciate you posting only the appropriate code and not all your code. \$\endgroup\$
    – House
    Mar 20, 2013 at 1:12
  • \$\begingroup\$ @Byte56 Not sure how profiling would help. There's no evidence of stutter in the values coming out of the update loop, so I'm pretty convinced the problem is between the game clock and the monitor, which isn't something I can test with a profiler. \$\endgroup\$
    – Thew
    Mar 20, 2013 at 1:38
  • \$\begingroup\$ I find it hard to believe you'd be seeing stutter from that. It's more likely a code performance issue or something else happening on your computer. You should also test it on a different machine. \$\endgroup\$
    – House
    Mar 20, 2013 at 1:43
  • \$\begingroup\$ @Byte56 Profiler looks completely normal. Not sure what I could be looking for though. It's definitely not a performance issue. I pulled the newTime and frameTime declarations out of the Update loop so there's zero memory allocation after initialization. Problem happens exactly the same so there's no way it's due to GC pauses. Whole program uses 13.5 MB of memory and uses between 20 and 24% CPU (entirely expected for a single thread on a quad-core CPU running the loop as fast as it can manage). \$\endgroup\$
    – Thew
    Mar 20, 2013 at 2:08

1 Answer 1

1
\$\begingroup\$

Maybe the problem lies elsewhere in the code, but if you're sure it's contained in the code you pasted, and the stutter is periodic, constant, and only happens in some tests (but constantly throughout the execution), you might be dealing with a rounding error, which would happen only if the initial time gets initialized to a specific value.

I see you're using doubles, but the MSDN documentation says that Stopwatch.ElapsedMilliseconds returns long. So I'd first change all time variables to long (yes even the dt, round it down to the nearest value), and see if the problem disappears.

Also, I don't know about XNA, but the modulo operator is for integers only, and to make it work with floating point values (and PI*2 will be a floating point value), instead of:

theta = (theta + spinSpeed * dt) % (Math.PI * 2.0);

You want fmod():

theta = fmod(theta + spinSpeed * dt, Math.PI * 2.0);

Tip: you might want to define a constant for Math.PI * 2.0 to save a couple of CPU cycles.

\$\endgroup\$
1
  • \$\begingroup\$ @tip: XNA has MathHelper.TwoPi \$\endgroup\$ Mar 20, 2013 at 11:52

You must log in to answer this question.

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