Advertisement

Stuttering problem - first attempt at fixed timestep game loop

Started by March 14, 2015 10:01 PM
6 comments, last by adt7 9 years, 9 months ago

Hi all, this is my first post as a member of this forum.

I'm playing around with SDL trying to get a smooth game loop implementation working. I've attempted the fixed timestep approach as described here: http://gafferongames.com/game-physics/fix-your-timestep/.

I'm having trouble with stuttering and I'm not sure how to proceed to work out what's wrong. The update function appears to be running at the correct rate and I have tried to interpolate the remaining time in the accumulator when rendering the scene.

  • MingW
  • SDL 2
  • Windows 7 64
  • Intel integrated graphics

Stuttering occurs in both windowed and full screen and with vsync on and off (it's significantly worse with vsync off).

When moving around the screen using the arrow keys, the background can be seen to stutter regularly.

Any help with this would be much appreciated. Any examples of smooth game loops would also be appreciated.


#include <SDL.h>

const int SCREEN_WIDTH = 600;
const int SCREEN_HEIGHT = 480;

const Uint32 TIMESTEP = 16;
const int SPRITE_SIZE = 64;
const double SPRITE_VELOCITY = 300 / 1000.0;

double gPosX = 0.0;
double gPosY = 0.0;

double gPrevPosX = 0.0;
double gPrevPosY = 0.0;

double gVelocityX = 0.0;
double gVelocityY = 0.0;

SDL_Renderer* gRenderer = NULL;

void handleInput()
{
	const Uint8* currentKeyStates = SDL_GetKeyboardState(NULL);

	gVelocityX = 0.0;
	gVelocityY = 0.0;

	bool leftKeyPressed = currentKeyStates[SDL_SCANCODE_LEFT];
	bool rightKeyPressed = currentKeyStates[SDL_SCANCODE_RIGHT];
	bool upKeyPressed = currentKeyStates[SDL_SCANCODE_UP];
	bool downKeyPressed = currentKeyStates[SDL_SCANCODE_DOWN];

	if(leftKeyPressed && !rightKeyPressed)
	{
		gVelocityX = - SPRITE_VELOCITY;
	}
	else if(!leftKeyPressed && rightKeyPressed)
	{
		gVelocityX = SPRITE_VELOCITY;
	}

	if(upKeyPressed && !downKeyPressed)
	{
		gVelocityY = - SPRITE_VELOCITY;
	}
	else if(!upKeyPressed && downKeyPressed)
	{
		gVelocityY = SPRITE_VELOCITY;
	}
}

void update(const Uint32 timestep)
{
	gPrevPosX = gPosX;
	gPrevPosY = gPosY;

	gPosX += gVelocityX * timestep;
	gPosY += gVelocityY * timestep;
}

void render(SDL_Point& spritePos)
{
	SDL_SetRenderDrawColor(gRenderer, 0x33, 0x33, 0x33, 0xFF);
	SDL_RenderClear(gRenderer);

	// Draw background

	SDL_SetRenderDrawColor(gRenderer, 0x00, 0x80, 0x80, 0xFF);

	bool iEven = true;
	bool jEven = true;

	for (int i=0; i<100; i++)
	{
		for (int j=0; j<100; j++)
		{
			if (iEven != jEven)
			{
				SDL_Rect fillRect = { i * SPRITE_SIZE - spritePos.x, j * SPRITE_SIZE - spritePos.y,
				                      SPRITE_SIZE, SPRITE_SIZE
				                    };
				SDL_RenderFillRect(gRenderer, &fillRect);
			}
			jEven = !jEven;
		}
		iEven = !iEven;
	}

	// Draw pink square

	SDL_SetRenderDrawColor(gRenderer, 0xCC, 0xAA, 0xCC, 0xFF);

	SDL_Rect fillRect = { SCREEN_WIDTH / 2.0f - SPRITE_SIZE / 2.0f, SCREEN_HEIGHT / 2.0f - SPRITE_SIZE / 2.0f,
	                      SPRITE_SIZE, SPRITE_SIZE
	                    };
	SDL_RenderFillRect(gRenderer, &fillRect);

	SDL_RenderPresent(gRenderer);
}

int main(int argc, char* args[])
{
	SDL_Init(SDL_INIT_VIDEO);

	SDL_Window* window = SDL_CreateWindow("Game Loop", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED,
	                                      SCREEN_WIDTH, SCREEN_HEIGHT, 0);
	gRenderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED | SDL_RENDERER_PRESENTVSYNC);

	bool quit = false;

	Uint32 accumulator = 0;
	Uint32 previousTime = 0;

	while (!quit)
	{
		SDL_Event e;

		while(SDL_PollEvent(&e))
		{
			if(SDL_QUIT == e.type)
			{
				quit = true;
			}
		}

		Uint32 currentTime = SDL_GetTicks();
		Uint32 elapsedTime = currentTime - previousTime;

		previousTime = currentTime;
		accumulator += elapsedTime;

		while (accumulator >= TIMESTEP)
		{
			accumulator -= TIMESTEP;

			handleInput();
			update(TIMESTEP);
		}

		float alpha = (float) accumulator / TIMESTEP;

		SDL_Point spritePos = { gPosX * alpha + gPrevPosX * (1 - alpha),
		                        gPosY * alpha + gPrevPosY * (1 - alpha)
		                      };

		render(spritePos);
	}

	SDL_DestroyRenderer(gRenderer);
	SDL_DestroyWindow(window);

	SDL_Quit();
}

Hello,

did you try to measure your FPS? I just tried out similiar code to yours (50*50 times SDL_RenderFillRect() on my GTX 560 TI) which gave me around 350 fps. I guess it might be too much for an integrated graphics card. My implementation of the fixed timestep is roughly like yours.

You could calculate the visible area to render only the part on the screen and just skip everything outside the viewport.

smile.png

Advertisement

Thanks for your reply!

I guess it might be too much for an integrated graphics card.

It knocks out 80+fps when vsync is off so unfortunately I don't think that's the issue. The stutter is also reproducible with only a single background quad :(.

[Edit: Ignore me, you are already doing what I suggested]

You're measuring time in milliseconds, so your maximum measurement error is <1ms.
Your update frequency is 16ms, so the maximum error as a percentage is (1ms/16ms=) <6.25% of a frame, which is quite large. You could try using a more accurate timer and see if that helps.


To track down stutter issues in my loop, I ended up printf'ing a lot of variables every frame - the current frame number, number of times update was called, amount of time simulated, about of time actually elapsed, time left in the accumulator, time taken by the render function, etc.
Try printing this out, and taking note of the current (approximate) frame number when you notice a stutter, and see if you can spot any irregularities, such as stutters corresponding to a sudden spike in the number of updates per frame, etc.

Thanks for your suggestions.

I've tried using Window's HPC and I think it's improved it a bit.

How much stutter would you say is inherent to 2D games? I've tried out a few 2D PC games and I can see some minor stuttering in them too at times.

I'm not sure if I've made a genuine programming error or whether I'm just too sensitive to it!

Advertisement
The only way to completely solve stuttering it to vsync to the refresh rate and make sure you never miss a frame.

All timing in all games is broken.
We measure the elapsed time of the previous frame, and use that to decide how much simulation to do in the next frame.

What we really want, is to predict how long the next frame will take to process ahead of time, and then simulate that amount of time.
This is an impossible feat... Unless every frame takes the same amount of time!

Have you tried making use of SDL_GetPerformanceCounter (alongside SDL_GetPerformanceFrequency) rather than SDL_GetTicks? It should give you a more accurate timing value.

This topic is closed to new replies.

Advertisement