Jump to content

  • Log In with Google      Sign In   
  • Create Account

FREE SOFTWARE GIVEAWAY

We have 4 x Pro Licences (valued at $59 each) for 2d modular animation software Spriter to give away in this Thursday's GDNet Direct email newsletter.


Read more in this forum topic or make sure you're signed up (from the right-hand sidebar on the homepage) and read Thursday's newsletter to get in the running!


Expert question: How to get a better time delta?


Old topic!
Guest, the last post of this topic is over 60 days old and at this point you may not reply in this topic. If you wish to continue this conversation start a new topic.

  • You cannot reply to this topic
30 replies to this topic

#1 Frank Force   Members   -  Reputation: 198

Like
7Likes
Like

Posted 31 March 2013 - 02:40 PM

Hi, 

 
I'm working on a game engine and something occurred to me about how accurately we are measuring the delta between frames.  I normally use a timer to check the delta, but even when running vsynced the measured delta is never exactly equal to the vsync interval.  On average it's equal to the vsync interval but the measured delta time can fluctuate quite a bit.  This means that if you have a fancy fixed time step with interpolation it's still going to be wrong because the delta that is being represented between frames will not ever be equal to the vsync interval, even though the frames themselves are always shown at exactly the refresh rate.  This causes a tiny bit if jitter that maybe most people don't notice but I do, and it was really starting to bug me. Clearly something must be done to correct the discrepancy, right?
 
Please understand that using a fixed time step with interpolation is not going to fix this issue!  What interpolation fixes is temporal aliasing, this is more like temporal fluctuation. The solution I worked out corrects the time delta in advance so it will always be in phase with the vsync interval. My game engine runs incredibly smooth with this enabled so I already know that it works.
 
My question is, am I crazy or is this kind of important for every game engine to have?  Is there some other more simple method I am unaware of that people use to deal with this?  I tried posting in a few other places but no one seems interested or maybe they just don't understand what I'm talking about.  One guy was very insulting and basically called me a noob... I've been working in the game industry at major studios for over a decade. If just one person can understand what I'm talking about and/or explain why I'm wrong that would be totally awesome.  Here's a link to my blog post with more info and code...
 

 



Sponsor:

#2 ApochPiQ   Moderators   -  Reputation: 16414

Like
1Likes
Like

Posted 31 March 2013 - 02:45 PM

Using a "fixed" timestep is not about expecting your timer to fire ever N milliseconds on the nose. Even when running fixed-step simulation you need to measure the actual wall clock time that elapses and use that to advance your simulation. Otherwise you will get clock drift and indeterminacy over time.

 

So yeah, you're basically right. I'm not sure what the question is, though, unless you're just looking to make sure you understand the situation correctly (which it seems you do).



#3 Frank Force   Members   -  Reputation: 198

Like
0Likes
Like

Posted 31 March 2013 - 02:54 PM

How do you measure the actual wall clock time that elapses?  I think that's what I'm already doing that I found was wrong. So, there are two different deltas here, the wall clock delta and the fixed time step delta.  The problem I'm talking about is that the measured wall clock is not ever an exact even multiple of the vsync interval even though it should be in order for the rendered frames to be interpolated properly.  Can you see how that would be an issue?



#4 ApochPiQ   Moderators   -  Reputation: 16414

Like
1Likes
Like

Posted 31 March 2013 - 02:59 PM

I don't see why you need some theoretical magic time value for anything to work correctly. Run your simulation at as close to fixed as you can, and you're done.

 

You will never get 100% precisely perfect timings on a consumer OS, they simply aren't designed to give you that guarantee. (Look at process timeslice quantization rules for example.) Design your code accordingly.



#5 Frank Force   Members   -  Reputation: 198

Like
1Likes
Like

Posted 31 March 2013 - 03:29 PM

You say it's not possible to get perfect timing, but you also say that doesn't really need that to work correctly.  I agree that a game engine will still work mostly correct without the stuff I'm talking about, every game engine I've ever seen has certainly worked fine without it.  But I don't understand why people are willing to settle for mostly correct, if we could get perfect timing that would be better right?  I mean, it's not theoretical or magical, monitors have a very specific vsync interval that images are displayed at.

 

The whole point of what I'm trying to talk about here is that you can get perfect timing that is 100% precise and this is how you do it. The only sacrifice is a small amount of unavoidable latency which is a small price to pay for perfect timing and smoothness of motion.  With triple buffering it doesn't really matter what the os does or how much fluctuation between deltas there is. What about my plan makes you think it won't yield perfect timing?



#6 Hodgman   Moderators   -  Reputation: 31939

Like
0Likes
Like

Posted 31 March 2013 - 07:00 PM

I've never thought of / tried this, but it does make sense in a vsync'ed system to only use deltas that are a multiple of the refresh rate...

When running at 60hz, what kind of deltas were you measuring befor implementing this system / what size correction deltas are you applying?
How is your timer itself implemented? Do you use 64bit absolute time as much as possible over 32bit deltas?

Last I checked in my game, when vsync'ed my deltas were 16.66667, which seems correct, but I'm still keen to check out your correction when I get a chance and see if it helps.

#7 ApochPiQ   Moderators   -  Reputation: 16414

Like
2Likes
Like

Posted 31 March 2013 - 07:49 PM

My point is that your corrected timing code is fine.

 

You will never get guaranteed time deltas without a hard-realtime OS. The kernel is free to schedule some other process for a quantum, which means you don't get a chance to even run when your timestep "should" be firing. This is why you get timing variances like you describe, and in a multitasking OS, it's actually a very good thing.

 

This compensation is actually relatively common in my experience; basically you're just eating the time variance by amortizing it across the subsequent frame(s).



#8 Frank Force   Members   -  Reputation: 198

Like
1Likes
Like

Posted 31 March 2013 - 08:13 PM

Hodgman - 

 

My deltas don't vary much, generally between 16 and 17 or so but can be much less/greater. Also when triple buffering multiple updates must sometimes happen during the same vsync interval, otherwise you aren't really buffering anything, right? My engine uses a double for the timer, but that shouldn't affect this issue.  Changing to a fixed point solution or increasing the timer accuracy won't get rid of the fluctuation. I am curious about why your measured deltas don't vary, have you tried logging them to a file?

 

ApochPiQ - 

 

Thanks that helps a little.  I like to think of it as buffering the time delta.  I figured this was a common thing that I must have never came across or seen mentioned anywhere because it seems pretty damn important.  That's why I'm asking if I am over thinking things or maybe just using the wrong terminology.



#9 Hodgman   Moderators   -  Reputation: 31939

Like
3Likes
Like

Posted 31 March 2013 - 08:53 PM

I am curious about why your measured deltas don't vary, have you tried logging them to a file?

Yeah I was mistaken, when synced to display at 60Hz, the delta log looks like:
...
0.016761
0.016591
0.016652
0.016698
0.016710
0.016666
...
Which over time seems to average out to the correct value, but yes, there is jitter.
[edit]They actually don't average out to 1/60 - they're generally higher than 1/60[/edit]
 
I am running my physics on a fixed time step of 60Hz (with some internal parts of the physics taking 10 sub-steps for each 1 physics tick), so it seems that occasionally I should actually be doing no physics updates for a frame, followed to two the next frame. Using a minimum delta of 1/60th (and the correction buffer) might smooth this out. Thanks.

Edited by Hodgman, 31 March 2013 - 09:18 PM.


#10 Cornstalks   Crossbones+   -  Reputation: 6991

Like
0Likes
Like

Posted 31 March 2013 - 09:25 PM

Interesting idea. I can see how this would help make things extra smooth. I'll have to try it out some time and see what the difference is like (I'm on OS X currently so I can't try your demo).

 

Question: is there a way to support monitors with different refresh rates? Most monitors these days are 60Hz, but there are some displays and systems that aren't running at 60Hz, but something "odd" (for example, some projectors may be 48 or 72Hz (since most movies are 24Hz)). If I'm understanding it right, you can't use this method and "properly" support monitors with different refresh rates (then again, the traditional fixed time step doesn't either).

 

I'm going to have to think about this one some more...


[ I was ninja'd 71 times before I stopped counting a long time ago ] [ f.k.a. MikeTacular ] [ My Blog ] [ SWFer: Gaplessly looped MP3s in your Flash games ]

#11 Frank Force   Members   -  Reputation: 198

Like
1Likes
Like

Posted 31 March 2013 - 11:49 PM

Hodgeman - Mine seems to fluctuate a bit more, but that seems about right.  I think I know why it averages higher then 1/60.  Any frames that get skipped will raise the average above the refresh rate.  Are you doing any kind of interpolation?  The correction buffer thing will ensure the delta is greater than 1 / refreshRate.  You don't want to cap it the minimum delta directly because even though the measured delta can be less then 1/60 it still needs to be accounted for.

 

Cornstalks - With a fixed time step you need interpolation to correct for the difference between your monitor's refresh rate and the fixed time step, that is a different issue.  This method should work fine regardless of the refresh rate of the monitor.



#12 Hodgman   Moderators   -  Reputation: 31939

Like
2Likes
Like

Posted 01 April 2013 - 03:28 AM

I added a bunch of logging to my timing code, and tried adding Frank's fixup.
Originally, I had an accumulator for a fixed time-step, and no interpolation (which simplifies things), very simple:

static float accumulator = 0;
accumulator += deltaTime;
const float stepSize = 1/60.0f;
while( accumulator >= stepSize )
{
	accumulator -= stepSize;
	DoPhysics( stepSize );
}

And then I added this 'snap to vsync' code above it:

static float buffer = 0;
float actualDelta = deltaTime + buffer;
float frameCount = floorf(actualDelta * 60.0f + 0.5);//I did this a bit differently - rounding to nearest number of frames
frameCount = max(1.0f, frameCount);
deltaTime = frameCount / 60.0f;
buffer = actualDelta - deltaTime;

Without the fixup, the accumulator would gradually increase it's remainder, until over about 1000 frames, a 'spare' 1.66ms of time builds up in the accumulator, and the physics loop runs twice in a single frame. Occasionally when this occurs, the next frame will only measure a delta of slightly less than 1/60th, e.g. 0.016654s, which means that no physics update occurs this frame. Then occasionally the next frame will be slightly over 1/60th, which when added to the accumulator, results in another two physics steps.
So typically, I'm getting 1 physics step a frame. Then once every few thousand frames, I take 2 steps in one frame, then 0, then 2, then back to normal. I hadn't noticed this small quirk, and only found it now due to this thread!
 
With the fixup, things are much more stable around this edge case. When the 'buffer' and/or accumulator build up enough extra time, then I still do two physics updates in a frame. However, the case where this is followed by zero the next frame is gone (and also the case where the 'zero' frame is followed by a 'two' frame is also gone).
 
So, from that, it seems to be a pretty good idea in general, and I think if I was using interpolation/extrapolation in my fixed-time-step code, then this fix would be even more important! As is, my step size matches my refresh rate, but in general I can't rely on this being true, so I need to add interpolation at some point. Without the fix, I'm guessing the original jittery timings would have a large impact on the perceived quality of the interpolation. Thanks frank biggrin.png

 

[edit]I also did some testing on what my actual average frame time was, but it was a bit inconsistant run-to-run. One run it averaged to 0.01667, another 0.01668, and another 0.01666... Also, at different times, the average would either be slowly creeping upwards or downwards.


Edited by Hodgman, 01 April 2013 - 05:32 AM.


#13 Frank Force   Members   -  Reputation: 198

Like
0Likes
Like

Posted 01 April 2013 - 09:34 AM

Hodgeman - Very interesting results!  Did you notice an actual visual pop when it did the 2-0-2 thing?  If you are triple buffering (and possibly double buffering i think) it is normal to get 2 updates or 0 in one frame,



#14 Hodgman   Moderators   -  Reputation: 31939

Like
0Likes
Like

Posted 01 April 2013 - 08:48 PM

Did you notice an actual visual pop when it did the 2-0-2 thing?

No, to me, my demo (which is a car racing game) felt silky smooth both before and after the fix... but this is a jitter the size of just one or two frames, occuring at most once every ~15s, and sometimes as low as once every few minutes.

 

I have a frame-counter variable which I used to add a hack where once every N frames, there would be a frame with 0 physics updates (followed by a frame with 2):

if( time.frame % N != 0) /* do fixed timestep update loop */

With a value like N=10, the game actually still felt quite smooth, and if I didn't know there was a problem, I don't know if I would've noticed that something was wrong.

With a value of N=3, it felt like a 30Hz game instead of a 60Hz game, or a bit like the experience of watching a 24Hz movie on a 60Hz TV.

 

But even at these extremes, it was still hard to tell what was wrong -- it just didn't feel like a 60Hz game (although Fraps will tell you it's rendering at 60Hz). It's only when I record a video of the game at 60Hz and then step through the video frame by frame that it's obvious that every 3rd frame is a duplicate of the one before it.

 

If you are triple buffering (and possibly double buffering i think) it is normal to get 2 updates or 0 in one frame

Well, I delimit my frames by calling Present -- to me that's the end/start of each frame. To the monitor, the start/end of a frame is the vblank.

Vsync should make the two match up, but in order for the GPU to end up with 3 frame buffers (2 with valid data, and one being written), then AFAIK the driver will not perform a vsync initially until the CPU is far enough ahead of the GPU. Once it has those 2 valid frames and a 3rd being drawn to, it will begin to actually vsync.

So ideally, I always have one physics update per "game frame", but there may be multiple of my "game frames" per vblank to begin with, until the necessary latency is achieved.


Edited by Hodgman, 01 April 2013 - 09:01 PM.


#15 Icebone1000   Members   -  Reputation: 1154

Like
1Likes
Like

Posted 02 April 2013 - 09:18 AM

One way I use to find the smoothness of my rendering, more intuitive then inspecting the variation of frame times, is to render a sprite model moving at constant speed in a loop, rendering it against the empty render target WITHOUT clearing it. That way the sprite will create a pattern on the screen, that pattern will be constant almost times, but when it jitters it will leave a gap on the pattern...can you understand it?

Heres my example:

https://lh3.googleusercontent.com/-pjanW27XNKY/UVrw6Nt0uyI/AAAAAAAAAgg/hundTIgOGaI/s1076/Untitled-1.png

I think its an osome way to check for jittering/stuttering or wathever its called..

Im current using Glenns method, my test loop current looks like that:

 

 

        //========================================================================
        // game loop vars
        //========================================================================
        double frameDeltaAccumulated = 0.0;
        const double fixedStep = 1.0/24.0;
        //========================================================================
        bool bGoingRight = true;
        while( Msg.message != WM_QUIT ){

            win::UniqueHigPerfTimer().Update();
            win::UniqueFileLogger()<<win::UniqueHigPerfTimer().GetDeltaSeconds()<<SZ_NEWLINE;

            while( PeekMessage( &Msg, NULL, NULL, NULL, PM_REMOVE )    ){

                TranslateMessage( &Msg );
                DispatchMessage( &Msg );

                win::UniqueFileLogger()<<"WM"<<SZ_NEWLINE;
            }

            //========================================================================
            // GLENN loop
            double currentFrameDeltaSec = win::UniqueHigPerfTimer().GetDeltaSeconds();
            if( currentFrameDeltaSec > 0.25 )//fixedStep )//0.25
                currentFrameDeltaSec = 0.25;

            frameDeltaAccumulated += currentFrameDeltaSec;

            while( frameDeltaAccumulated > fixedStep ){

                frameDeltaAccumulated -= fixedStep;
            //========================================================================
            
                mySprite.m_trafo.Step(); //saves previous trafo for interpolation

                if( bGoingRight ){

                    const DirectX::FXMVECTOR vVel = DirectX::XMVectorSet( (float)(100.0*fixedStep), 0.0f, 0.0f, 0.0f );
                    mySprite.m_trafo.m_vCurrentPosition = DirectX::XMVectorAdd( mySprite.m_trafo.m_vCurrentPosition, vVel);

                    if( DirectX::XMVectorGetX( mySprite.m_trafo.m_vCurrentPosition ) > 500.0f ) bGoingRight = false;
                }
                else{
                    const DirectX::FXMVECTOR vVel = DirectX::XMVectorSet( -(float)(100.0*fixedStep), 0.0f, 0.0f, 0.0f );
                    mySprite.m_trafo.m_vCurrentPosition = DirectX::XMVectorAdd( mySprite.m_trafo.m_vCurrentPosition, vVel);

                    if( DirectX::XMVectorGetX( mySprite.m_trafo.m_vCurrentPosition ) < -500.0f ) bGoingRight = true;
                }
            }

            //------------------------------------------------------------------------
            // interpolate:

            const double alpha = frameDeltaAccumulated / fixedStep;

            mySprite.Update( alpha, myWindow.m_device.GetContext() );

            //------------------------------------------------------------------------
            myWindow.m_spriteRenderer.Render( &mySprite ); // queue sprite commands
            myWindow.m_spriteRenderer.Raster( myWindow.m_device.GetContext()); // execute commands

            myWindow.m_swapChain.m_pSwapChain->Present(1, 0);        
        }
 

Im totally new to fixed time steps, previously Id just gather the delta with the hig perf counters and update with it.

 

Heres how my deltas look like(im rendering a single sprite, and my deltas are far from constant):

 

0.016912
0.016572
0.016887
0.016759
0.016487
0.015790
0.016446
0.016867
0.016665
0.016744
0.016644
0.016964
0.016686
0.016564
0.016661
0.017432
0.016770
0.016777
0.016366
0.017165
0.017188
0.016933
0.016495
0.016738
0.016733
 

 

I will try your method later.


Edited by Icebone1000, 02 April 2013 - 09:25 AM.


#16 Frank Force   Members   -  Reputation: 198

Like
1Likes
Like

Posted 02 April 2013 - 12:20 PM

Hodgeman - I'm not surprised your game already felt smooth because using a fixed time step that is equal to the vsync is the ideal case to not have jitters.  Its interesting that tried that method to force it to produce duplicate frames and still couldn't really detect it visually.  It does depend on the type of game, for some games it is less obvious.  The most obvious case is a 2d scrolling game where pops like this are very apparent.  In 3d fps type games it seems less obvious.  I also think that many of even the very best games have this jittery issue so we are trained to not notice it.

 

I think the whole point of triple buffering is to allow the CPU to fall behind a bit to smooth out pops by sacrificing latency.  So even if you have just 1 new backbuffer to display and the vsync happens then it will flip it, why wouldn't it?  The next vsync you will just need to do 2 frames to catch up.  This is how pops are smoothed out even if you have a very long frame.  Triple buffering + vsync is a way of actually decoupling the vsync from the game update.  Even though you get 1 update per vsync on average, it's really more about keeping the back buffers updated then waiting for the vsync.

 

Icebone - Wow, that's a really cool idea!  Keep in mind though that due to rounding that gaps in the pattern may actually be correct.  So for example if it's moving at 1.1 pixels per vsync, you would expect it to jump an extra pixel every 10 vsyncs. The math to make things move an integer number of pixels when there's a fixed time step with interpolations is a bit complicated.  What I do to visualize jitter is have a wrapping image scroll continuously across the screen.  When running at 60 fps it should move extremely smooth.  I will need to think more about your idea though, I like how it is visualized over time.



#17 Norman Barrows   Crossbones+   -  Reputation: 2357

Like
0Likes
Like

Posted 02 April 2013 - 01:26 PM

No, to me, my demo (which is a car racing game)

 

full physics?

 

PC platform?

 

available when?

 

(pardon the interruption, but i'm curious)


Norm Barrows

Rockland Software Productions

"Building PC games since 1989"

rocklandsoftware.net

 

PLAY CAVEMAN NOW!

http://rocklandsoftware.net/beta.php

 

 


#18 Norman Barrows   Crossbones+   -  Reputation: 2357

Like
0Likes
Like

Posted 02 April 2013 - 01:35 PM

But even at these extremes, it was still hard to tell what was wrong -- it just didn't feel like a 60Hz game (although Fraps will tell you it's rendering at 60Hz). It's only when I record a video of the game at 60Hz and then step through the video frame by frame that it's obvious that every 3rd frame is a duplicate of the one before it.

 

30 fps was the original "industry standard" (or target) for how fast a game should run for smooth animation. but that was a minimum speed, not the fastest rate at which humans can perceive a change.

 

this is what you were experiencing when it didn't "feel" like 60 fps.  sure you have to do screen dumps to explicitly see it. but on a lower, automatic, subconscious level your brain already perceives it, although you may not be able to quite articulate what you're brain is recognizing.

 

human brains appear to be quite good at picking up on subtle things like this, especially in graphics, since we're visually oriented creatures.


Norm Barrows

Rockland Software Productions

"Building PC games since 1989"

rocklandsoftware.net

 

PLAY CAVEMAN NOW!

http://rocklandsoftware.net/beta.php

 

 


#19 Norman Barrows   Crossbones+   -  Reputation: 2357

Like
0Likes
Like

Posted 02 April 2013 - 01:41 PM

Keep in mind though that due to rounding that gaps in the pattern may actually be correct.

 

 

"don't clear the screen, draw trails, and measure the gaps" should tell you if you're accurate enough for a given resolution. sounds super simple to me, just turn off one line of code (clearscreen).


Norm Barrows

Rockland Software Productions

"Building PC games since 1989"

rocklandsoftware.net

 

PLAY CAVEMAN NOW!

http://rocklandsoftware.net/beta.php

 

 


#20 Hodgman   Moderators   -  Reputation: 31939

Like
0Likes
Like

Posted 03 April 2013 - 03:23 AM

full physics? PC platform? available when?
(pardon the interruption, but i'm curious)

Yes, it's a fully realistic car simulation engine, but we're using futuristic/concept car parameters instead of current realistic car values. Windows PC to begin with. Aiming for an alpha/prototype around September.
Placeholder site is 22series.com, or the company sites in my sig ;-)
[/off topic]




Old topic!
Guest, the last post of this topic is over 60 days old and at this point you may not reply in this topic. If you wish to continue this conversation start a new topic.



PARTNERS