the 'perfect' game loop, fix your time step (by step)

Started by
31 comments, last by cozzie 7 years, 3 months ago

It reads like you're not clear on the difference between update rate (logic) and frame rate (rendering). The 'fix your timestep' approach is designed to lock your update rate while leaving your frame rate to go as fast as your hardware will allow. With the fixed timestep it is certainly possible to see movement appear to be jerky, due to aliasing between the render rate and the update rate, which can be fixed by either making the update timestep a lot smaller (so the aliasing is less pronounced) or by using the blend factor (which attempts to interpolate across the aliasing).

Vsync is a slightly different issue; obviously if your program says "do nothing until vsync" then the whole loop will take longer to execute. But the next time you get to the update section, you'll see more time will have passed, and will perform more update iterations accordingly.

I've been working on some console titles recently and there, it's common to have a fixed frame rate of 60Hz or 30Hz. If you're not rendering fast enough to hit 60Hz, you assume 30 and can double up the updates to match. If your updates or rendering takes too long for 30Hz, you're screwed and need to fix your code or assets, because you're not allowed to run at 20Hz. In some ways, it's a lot simpler!

Advertisement

@Kylotan: thanks, I'll take into consideration if I fix the framerate at 30 or 60hz, depending on the variables/ situation (if rendering can catch up with 60fps easily, then I'll stay with 60 for example). Regarding vsync, I understand what you mean, but when I implement the 'blending' correctly, enabling/ disabling vsync shouldn't affect the feel of the game, aka updating objects. Would you agree?

@NormanBarrows: I didn't see your response on when to apply the 'blending'. Are you saying I could also do it like this?


while(accumulator >= fixedFrameTime)
{
	float alpha = accumulator / fixedFrameTime;
	if(!Update(fixedFrameTime, alpha, mTimer.GetTotalTime()))
	{
		CLOG(FATAL, "GAMEBASE") << fatal_update_appl.c_str();
		return(int)msg.wParam;
	}
	totalTime += fixedFrameTime;
	accumulator -= fixedFrameTime;
}

// Update(): lerp oldState and newState using Alpha (newState = created)
// Render(): takes newPos/state and use that for rendering
// to decide: where to update matrices, in render sounds best (no overhead) 

This means Render() doesn't need to have/ know the alpha for the blending, because it's already done within Updating.

A potential con; when the while loop is execution more then once, I'm doing some updating/ blending calculations multiple times, which only has to be done once when I do this at render. For sure I can do the matrix creation within render, because I only need those when I'm actually going to render. Unless in the future I need updated world matrices of mesh instances, for other purposes then the actual rendering.

Crealysm game & engine development: http://www.crealysm.com

Looking for a passionate, disciplined and structured producer? PM me

enabling/ disabling vsync shouldn't affect the feel of the game, aka updating objects. Would you agree?

No, because the feel of the game is about the whole feedback loop from input to output. Vsync (or absence of it) affects both the rate at which the game is rendered, and potentially the jitter affecting that rate (depending on what else is happening), and any aliasing caused by the rendering rate differing from the update rate can be observed in the apparent movement of objects. So while vsync doesn't affect the updated simulation at all, it certainly can affect your perception of it. Blending can improve this but not necessarily solve it (because who's to say linear interpolation is correct? What if the objects are rotating?) Whether this matters or not depends on how much aliasing you have, which be lower if you can make your update timesteps as short as possible. It's not uncommon for physics to run at 100Hz or more for this reason (although you wouldn't want to run your whole game logic that frequently).

Your example to NormanBarrows there is just reverting to a non-fixed update timestep, with all the complexity of a fixed timestep. Plus a hidden bug in that you're using the alpha on every step. Don't do it. Go fixed for stability and reliability, or go variable for simplicity.

Thanks. Regarding the 'option 3' I already thought it didn't sound 'logical' (to blend before all updating is finish).

For that option I'll stick with the code I pasted somewhere in the end of page 1 (with the 3 options), with the addition of the blending, within render.

When you think about it; console development does bring some nice advantages with hardware always being the same :)

There are also opinions that say: don't support disabling vsync and use use a variable deltaT (option 1). I believe the disadvantages on this, are that more 'experienced' PC users can overrule it through graphics properties and that I don't know how consoles handle vsync (btw; if my dreams come true, the aimed platforms are PC, XBone and PS4).

Crealysm game & engine development: http://www.crealysm.com

Looking for a passionate, disciplined and structured producer? PM me

Option 1 is the way many games worked for years, and it's perfectly fine until you throw physics simulations into the mix. There's a lot of articles on the early Quake games where people realised they could jump higher at lower frame rates, because of this. Decoupling and fixing the physics step was the solution for this, hence the Fix Your Timestep article. Vsync is only a factor there because it changes the rendering rate which in turn would change the update rate. Decouple updates from renders and vsync ceases to be a factor (in your update calculations, at least).

Do you know any other ways to achieve this? (a fixed framerate)

framerate limiter algo:

while !quitgame

{

start_timer

render

input

update

while elapsed_time < desired_frametime

{

// do nothing

}

}

a high resolution timer based on QueryPerformanceCounter is used.

desired_frametime = 0.01666666 seconds for 60 FPS.

this loop will run no faster than 60 fps, and will degrade gracefully at < 60 fps, when under heavy load (from either render or update).

fix-your-timestep drops frames when ET > DT. a low ET cap can prevent this.

and NEVER use Sleep().

Norm Barrows

Rockland Software Productions

"Building PC games since 1989"

rocklandsoftware.net

PLAY CAVEMAN NOW!

http://rocklandsoftware.net/beta.php

watch for tearing when vsync is disabled.

i think i notice some on my new i7-6700K gtx1080 running Rome II Total War Emperor Edition on ultra graphics settings.

When you scroll across the map (especially vertically) you get some distortion or tearing around the middle of the screen.

FYI: Rome II Total War Emperor Edition on ultra graphics settings uses more power (watts) than ANY other software i've run on the new PC. More than Skyrim SE, more than Mad Max (a great game, BTW). The campaign mode map uses just about every special graphics effect in the book.

if you want to maxout FPS, use fix-your-timestep. if you just want to target 30fps or 60fps, a basic framerate limiter might be easier.

EDIT:

i'm actually quite pleased with the user defined framerate limiter i'm using in Caveman right now. The user sets a desired FPS. the game calcs the corresponding desired_frametime for the framerate limiter, and movement_scalefactor used in update. that way the user can set it as high as their PC can run at a consistent framerate, which actually seems to be more important that a fast framerate. there's no sprial of death, no temporal aliasing, and no dropping of frames. there's no need to save old_state, and no need to blend old_state and current_state. and render always draws the current state, not some state in-between the last and current ones. by setting desired FPS close to what fix-your-timestep would run at, you get very similar behavior under normal conditions, and it degrades gracefully as the load increases.

Norm Barrows

Rockland Software Productions

"Building PC games since 1989"

rocklandsoftware.net

PLAY CAVEMAN NOW!

http://rocklandsoftware.net/beta.php

found out last night the graphics artifacts i see in Rome2 is screen tearing. Vsync is off.

also just posted a few new thoughts on fix-your-timestep in my dev info journal:

https://www.gamedev.net/blog/1729/entry-2262573-increasing-et-accuracy-in-fix-your-timestep/

https://www.gamedev.net/blog/1729/entry-2262574-thoughts-on-fix-your-timestep-and-the-spiral-of-death/

Norm Barrows

Rockland Software Productions

"Building PC games since 1989"

rocklandsoftware.net

PLAY CAVEMAN NOW!

http://rocklandsoftware.net/beta.php

I've know implemented the 'Fix your timestep' approach, where basically I can guarantee the update rate (fixed) and let rendering be done as fast as the user/system allows. No difference in expierence/ functional results with or without vsync (besides potential tearing). Even without the 'alpha' / lerping between the current and laststate the result is smooth. I might add it later, but I'll do that if/ when I know my final structure (where to store and update matrices etc.).

As a side question; what other purposes do you have with the world matrices, other then rendering?

So far for collision detection I use bounding volumes, not matrices. But ofcourse the bounding volumes will also have to be either based on 'newstate' or 'oldstate' (or lerped in between). Currently my view is that the lerped state is only for rendering, and that everything else simply uses the newstate.


int CAppl::Run()
{
	MSG msg = {0};
	mTimer.Reset();
	
	float fixedFrameTime = (1000.0f/60.0f);		// 60fps - 16.67ms per frame
	double accumulator = 0.0;

	bool run = true;
	while(run)
	{
		mTimer.Tick();
		CalculateFrameStats();
		mInputHandler.UpdateKeys();
		
		while(PeekMessage(&msg, 0, 0, 0, PM_REMOVE))
		{
            TranslateMessage(&msg);
            DispatchMessage(&msg);
			if(msg.message == WM_QUIT) run = false;
		}

		if(!mAppPaused)
		{
			double lastFrameTime = mTimer.GetDeltaTime();
			if(lastFrameTime > 250) lastFrameTime = 250;		// max 0.25 sec, for breakpoints etc.

			accumulator += lastFrameTime;
		
			while(accumulator >= fixedFrameTime)
			{
				if(!Update(fixedFrameTime, mTimer.GetTotalTime()))
				{
					CLOG(FATAL, "GAMEBASE") << fatal_update_appl.c_str();
					return(int)msg.wParam;
				}
				accumulator -= fixedFrameTime;
			}

			// float alpha = accumulator / fixedFrameTime;
			// optional/ add: pass alpha to Draw to blend last and current state (using lerp)
			// meaning: 2 states in meshInst class, don't update position yet, in Render() update members with blended last-curr state
			// (also Render must then update matrices too; sending to GPU buffers is already in Render)

			if(!Render())
			{
				CLOG(FATAL, "GAMEBASE") << fatal_render_appl.c_str();
				return(int)msg.wParam;
			}
		}
		else
		{
			Sleep(100);
		}
    }
	return (int)msg.wParam;
}

Crealysm game & engine development: http://www.crealysm.com

Looking for a passionate, disciplined and structured producer? PM me

What does mTimer.Tick() do? Or, perhaps more specifically, how does mTimer.GetDeltaTime() work?

As for world matrices, they can represent position and orientation, and therefore be useful all over the game.

This topic is closed to new replies.

Advertisement