Smoothing Delta Timing

Miscellaneous Forums/General Discussion/Smoothing Delta Timing

Hello,

I`m trying to smooth out the following Delta Timing code so that the TimePassed value doesn`t keep fluctuating which gives me slightly jerky movement.

What`s the easiest way to achieve this?

Jason.


Graphics 640,480
SetBuffer BackBuffer()

Global StartTime#=0
Global EndTime#=0
Global TimePassed#=0

While Not KeyHit(1)
	Cls
	
	EndTime#=MilliSecs()
	TimePassed=EndTime#-StartTime#
	StartTime#=EndTime#
	
	x#=x#+0.1*TimePassed#
	If x#>640 Then x#=0
	
	Rect x#,100,50,50
	
	Flip
Wend
End



Error diffusion?

EndTime#=MilliSecs()


MilliSecs() works ONLY with integers !

Here is why you need integers. A float cannot exactly represent a large integer value. If your computer has been running for at least 2^24 milliseconds ( about 280 minutes ) then timer values are already too big for a float.

After a time difference has been calculated you can use a float for the resulting small integer value.

This example shows the time calculation failing with floats. It will work if msec1 and msec2 are integers.
msec1# = 16800000   ; Two large integer values. As floats they are
msec2# = 16800001   ; equal so the difference is zero.

mdiff# = msec2 - msec1

Print "delta time = " + mdiff

WaitKey


How much does your framerate fluctuate?

I came up with an algorithm not long ago that made the framerate super smooth, but I found that it didn't make the stuff in the game look like it was moving any more smoothly. I couldn't tell the difference at all with a scrolling star background whether it was moving at a rock solid 60fps, or whether it varied by 5fps up and down.

But if you want to try this setup, here's the code:

	Global Time%, Time_Delta%, Time_Delta_Sec#, Last_Time_Delta%, Last_Time_Delta_Sec#
	Global System_Time%, Last_System_Time%
	Global FPS%
	Global State%
	
	Global Frames% = 1
	Global FrameTime%[Frames]
	Global Max_FrameTime%
	Global Frame%
	Global FrameDelay%

					
' Main:

	App.Create("Attack of the Alien Space Beetles!", 800, 600, 0, True, False)

	SeedRnd MilliSecs() 
	System_Time = MilliSecs()
					
	Repeat
	
		Repeat 
			Last_System_Time = System_Time										' Store start time of last frame.
			System_Time 	 = MilliSecs()										' Get the current system time.  
			Time_Delta       = System_Time - Last_System_Time					' Calculate how long last frame took to render, in milliseconds.
			Time_Delta_Sec#  = Float(Time_Delta)/1000.0							' Convert frame time to seconds.
		Until (Time_Delta > 0) And (Time_Delta < 250)							' Handle unnaturally long pauses between frames gracefully.
								
		EventHandler()															' Process window events.
		
		If Not AppSuspended()
									
			Time = Time + Time_Delta											' Calculate current game time.  
						
			If App.Windowed Then ActivateGadget App.Canvas						' If windowed, make sure canvas stays active, or else polled input will go elsewhere.		

			UpdateMouse()														' Get mouse input.
			Gameplay.Update()													' Update game.
			
			Animate.UpdateAll(Time_Delta)										' Animate sprites.

			SetClsColor 0, 0, 0													' Clear screen and draw sprites.
			Cls
			Sprite.DrawAll()				
									
			If KeyHit(KEY_F) Then App.ToggleFullscreen()						' Toggle fullscreen/window mode when user presses F.  (May need to disable this during user input.)
			
			Delay(40)
			
			'Last_Time_Delta      = Time_Delta									' Store timestep for this frame so we can reference it next frame.
			'Last_Time_Delta_Sec# = Time_Delta_Sec#

				
			' Smooth out framerate.
				
				If Not KeyDown(KEY_S)
				
					' Find the longest amount of time a frame took to render over the last few frames.	
								
						Max_FrameTime = 0
						For Frame = 0 To Frames-1
							If FrameTime[Frame] > Max_FrameTime Then Max_FrameTime = FrameTime[Frame]
						Next
					
					' Shift all the elements toward the end of the array by one.	
						For Frame = Frames-1 To 1 Step -1
							FrameTime[Frame] = FrameTime[Frame-1]
						Next					
					
					' Resize the array to have 1/8 as many elements as we are rendering frames per second.
					
					' The reason we do this is so that no matter what the framerate is, the system will adjust to big changes in framerate within 1/8
					' of a second.  The response time is only a concern so far as the system would render at a lower framerate for longer than it
					' really needs to.
					
						If Max_FrameTime > 0 Then Frames = Floor((1000.0 / Max_FrameTime) / 8.0)
						If Frames = 0 Then Frames = 1
										
						FrameTime = FrameTime[..Frames]						
					
					' Calculate how long this frame took to render.
						Time_Delta = MilliSecs()-System_Time
					
					' Add this frame's time to the start of the array.
						FrameTime[0] = Time_Delta
				
					' Delay this frame to make it last as long as the longest frame in our timeframe.
						If Max_FrameTime < 70
							While Time_Delta < Max_FrameTime
								Time_Delta = MilliSecs()-System_Time
							Wend
						EndIf
						
				EndIf

			' Draw framerate if user presses tab.

				If KeyDown(KEY_TAB) 
					
					If Time_Delta > 0 
						FPS = 1000.0 / Float(Time_Delta)
					Else
						FPS = 1000
					EndIf
													
					DrawText FPS, 16, 16										
					DrawText Frames, 16, 32
					
				EndIf
			
			' Flip new frame into view.
				Flip 1
					
		EndIf 
			
	Forever



It might work well if you have really big framerate jumps and low framerates. But it didn't provide any noticeable difference at the super high framerates I am getting in BlitzMax.

Oh and you uh, might want to take that delay out of there. :-)

Or you can use doubles isntead of floats. (I do for my timing)

Anyway I'm interested in doing the same sort of thing for my framework soon. Basically on my PC everthing is dead smooth (delta is the same each frame), but it's not the same on *some* other PCs.

Some people have frequent peaks followed by troughs (in BMax), so like instead of it taking 16ms per frame (@60Hz) they get 48ms for one frame followed by two frames of 0ms! This really screws with the game smoothness because the frame before the peak will get shown for at least 48ms and then the delta time says "whoa, better make a big jump to catch up" and it shifts the object a long way.

I found that it actually looks better if delta time doesn't catch up as much (or at all). But if you don't catch up at all, and you have a game timer, it'll loose time. So I'm wondering about catching up say half the amount, and then spreading the other half over the following frames (the 0ms frames for example). But I haven't implemented it yet.

Don't know if that makes sense...

I read swift's code and it appears to react to changes in FPS over 1/8 second. My proposal would be to take instant action and then spread the "load" over the next few frames. Don't know if it'll work yet...

It still won't look perfect because at the end of the day, if 3 frames are missed (for example) because of a background task, the object is still going to sit there for 3 frames before jerking forward. It's just how *much* it jerks that I'm intending to modify.

I'm interested that you are getting jerks in BPlus. I was beginning to think that jerks only existed in BMax. I was going to write a Bplus version of my jerk tester, but perhaps you could convert this code?

'SetGraphicsDriver GLMax2DDriver()
Graphics 800,600,32
'Graphics 800,600,0

Local t1:Int
Local t2:Int
Local test: Int
Local high: Int
Local lo:Int = 200
Const MAXVALUES=800
Local values:Int[MAXVALUES]
Local counter=0
Local loops = 0
Local average = 0
Local total = 0

While Not KeyHit(KEY_ESCAPE)
	t1=MilliSecs()
	Cls
	DrawText "Current: "+test,0,0
	DrawText "High:    "+high,0,20
	DrawText "Low:     "+lo,0,40
	DrawText "Average: "+average,0,60
	DrawText "Press <Space> to reset High",0,80
	DrawText "Press <Escape> to exit",0,100
'	DrawText GCMemAlloced(),0,80
	For Local i=0 To MAXVALUES-2
		SetColor 255,255,0
		DrawLine 0,500,800,500
		SetColor 255,255,255
		DrawLine i,500-values[i],(i+1),500-values[i+1]
	Next
    Flip 1	
	t2 = MilliSecs() 
	test:Int = (t2-t1) 
	If test>high Then high = test
	If test<lo Then lo = test
	If KeyHit(KEY_SPACE) 
		high = 0
		lo = 200
		loops = 0
		average = 0
		total = 0
	End If
	values[counter] = test
	Counter:+1
	If counter>=MAXVALUES Then counter = 0
	loops :+ 1
	total :+ test
	average = total/loops
Wend


See if you get a smooth line (give or take 1 pixel) or a line with peaks and troughs.

One small point, maybe you are just seeing moire effect on your moving objects. For example, if something moves exactly 1 pixel per frame and your frame rate is 60HZ, it will always look dead smooth (unless a background task prevents it moving for a frame or two). However, using delta time means that some frames it may move 1 pixel, and others 0 and others 2 etc. (due to rounding a float to integer coords) The net result is that it will have moved an average of 1 pixel per frame (if that's the speed you've set it to), but it may look a tiny bit jerky.

If you use BMax it can draw at NON-Integer coords using a bit of video card jiggery pokery (smoothing) and that can look pretty neat (if you setup your sprites to handle that).

Grey:
What my code does keep track of the frame times for all frames over the last 125 milliseconds, and then delay the current frame so that it takes as long to render as the longest time a frame took to render in that timespan.

In other words, if the current frame took 20ms to render, and five frames were rendered over the last 125 milliseconds, and the last five frames had times of [25, 20, 20, 20, 20] then the current frame would be delayed for 5 milliseconds, and the array of frame times would be updated to read [20, 25, 20, 20, 20].

Note that that 25 there has to move off the right side before any frame will take less than 25ms to complete, and the times stored in the array are the actual real times each frame took to render.

So with this setup, the system reponds instantly to drops in framerates, and speeds back up after 125ms if the framerate goes back up. This keeps the framerate smooth, but doesn't cause the action to go all choppy for a really long period of time if there's a framerate glitch and one frame takes a really long time to render. (Note that with my timing loop, any frame that takes over a quarter of a second is ignored, to handle alt-tabbing and other things that cause framerate glitches.)

That's a good explanation thanks. Thing is, if you get a single frame of say 50ms then the next few frames will also take that long (although there'll only be 2.5 of them in 125ms of course). What is the advantage of delaying future frames even though they might be safe to run at full frame rate again? Perhaps it should pick an average not the highest? (although in that case you'd need to populate the array with sensible "expected" values right at the start of the game.

I guess the 125 sample time period could also be tweaked. But as you say, you didn't really notice any difference anyway...

The advantage is you smooth the framerate. What if your framerate is 12, 16, 12, 16 (ms)? You can't make the 16's speed up, so the only way to smooth it out is to slow down the 12's.

Of course it's not perfect. If you get just one 50ms frame, and the rest are normal, then you'll get a few frames that are slower that don't need to be. But if that concerns you so much, then you can always modify the way you select the "slowest" frame, so that it doesn't pick the slowest if there's only one frame there. Maybe you could pick the second slowest?

I kinda doubt you'll have many cases through where you get just one slow frame. Most likely you will get several slow frames in a row if the hard drive is being accessed or the user tabs out. Good luck determining what exactly caused the slowdown and whether you should ignore it or not. :-)


Picking the average is one of the things I tried. I also tried picking the median. Neither resulted in a rock solid fps. This method does. The FPS counter in the corner of the screen will sit at one number for a really long time, as if you're averaging it over a long time period. All the other methods I tried allowed it to still fluctuare by one or two milliseconds. But one or two millisecond fluctuations weren't noticable.

hmm interesting. You often do get one slow frame btw, the jerktester graph program I posted about shows mini peaks caused by god knows what (on some systems, not on mine). If those peaks were to be continued at a low FPS by the code it might not be so cool. I guess I need to find a crap PC to test this stuff out on.

Quicksilva: Did you ever resolve your monitor problem a while back where it was all jerky and horrible?

Firstly thanks for the help guys, I now have a better idea of what I`m doing.

Grey Alien: Not really no. I`m fine if I run games in my monitors native resolution of 1440x900 but if I try to run them at say 800x600 in a window the game is as jerky as hell. After a bit of research I`m not the only one this is happening to either, full screen modes are unaffected it`s just windowed modes other than the monitors native screen size. This does not happen with a VGA connection just a DVI one but most users with this option will obviously be using it for a clearer picture.

Jason.

wow so the problem still persists, bummer. So you are saying the problem is if you still have the desktop in 1444x900 but you run in an 800x600 window (can't see why that would fail, weird)? But full screen games are fine?

Yep, exactly. Strange problem indeed. I`m stumped and have tried everything including using older drivers, which seemed to cure the problem slightly but not completly.

Another thing to point out is that it is only Blitz windowed games and apps. All other windowed games work fine so it must be something Blitz related I`m guesiing?

Jason.