Sign in to follow this  
X Abstract X

How Exactly Does VSync Work?

Recommended Posts

I'm very confused. I have a test app, just a basic cube in OpenGL with the ability to rotate it using the keyboard. I capped the logic of my application to 50 frames per second but let it render as fast as possible. Just out of curiosity I added a counter at the end of my main loop to count the amount of times the main loop is iterated. Now, my monitor has a refresh rate of 60hz and with VSync enabled I determined that my main loop was iterated ~110 times per second. With VSync disabled the main loop completed 1400 iterations per second on average. My code is basically like this:
main {
    if (isTimeForNextFrame()) {
        input();
        logic();
    }

    render();
    counter++;
}


My question is, what's with these numbers? What exactly does VSync do to cap the rendering and why is it affecting my main loops iterations per second? [Edited by - X Abstract X on April 30, 2010 11:42:30 PM]

Share this post


Link to post
Share on other sites
SwapBuffers blocks until it renders :( You could run your renderer in a separate thread. I think there's also some utility function provided with OpenGL that allows you to check if there's currently a buffer being rendered (thereby allowing you the chance to skip calling SwapBuffer altogether).

Share this post


Link to post
Share on other sites
So 60 times a second, the contents of your video memory is pushed to your monitor to be displayed on the screen.

If you changed video memory while it was being pushed to the screen youd get half of one frame and half of another frame.

60 times a second is fast but this problem is EXTREMELY noticeable and is called "tearing".

anyhow, what vsync does is wait til the screen refresh is done and then alters the video memory (by copying the back buffer to the front buffer) so that it has plenty of time to update the memory before it's shown again.

That eliminates tearing by making sure that by the time the monitor refresh happens, that the video memory is not in a state of change (:

Share this post


Link to post
Share on other sites
Quote:

What exactly does VSync do to cap the rendering and why is it affecting my main loops iterations per second?


See vertical synchronization. Quite simply, vsync waits until the display device finishes rendering a frame before pushing a new frame to the display (prevents page tearing). This wait is affecting the iterations per second. However, you shouldn't worry about how many times a second your loop is executed, since you're regulating the logic frequency. In other words, it is guaranteed that the application states of two machines running at different speeds (provided they are running at an frequency equal to or greater than your logic frequency) will be the same at any given time.

Also, if you're not performing interpolation of any sort in between logic frames, then you really aren't gaining anything by rendering as fast as possible, since the render frames in between logic frames will basically be the same image. [smile] Besides, if your display device can only present 60 unique images a second, why should you try to feed it 1000 unique images a second if only 60 of them will actually be displayed?

EDIT:
"Fix Your Timestep" is an excellent read on the subject of regulating update frequencies yet rendering as fast as possible.

[Edited by - _fastcall on May 1, 2010 1:56:15 AM]

Share this post


Link to post
Share on other sites
If swapbuffers() blocks until it renders, why is my main loop executing 110 times and not 60 times?

@_fastcall: Hmm, I was reading "Fix Your Timestep" earlier and it seems to me that it is only really useful for physics intensive games to interpolate. In my current set-up I accumulate the delta time for each frame and only render if the logic is keeping up with the specified fps.


bool isTimeForNextFrame() {
unsigned int frameStartTime = SDL_GetTicks();

unsigned int deltaTime = frameStartTime - _previousFrameStartTime;
_previousFrameStartTime = frameStartTime;

_accumulatedTime += deltaTime;

if (_accumulatedTime >= _msPerFrame)
_frame++;
_accumulatedTime -= _msPerFrame;
return(true);
}

return(false);
}

while (gameRunning) {
while (isTimeForNextFrame()) {
logic();
}

render();
}



Does doing that make any sense?

Share this post


Link to post
Share on other sites
"Fix Your Timestep" doesn't apply exclusively for intense physics games, it applies to any game that wishes to ensure that the simulations are accurate across multiple machines of varying speeds while achieving high frame rates at the same time. For instance, the setup you have right now, won't run smoothly if you set the logic update frequency to update once per second, even though you're rendering the same state more than a thousand times per second. If you interpolate between the two states, you get the appearance of running the game at thousands of frames per second, even though your logic is only being updated once per second. Here's a setup I used for the pong game:


static const int ticksPerSecond = 1000; // How many ticks in a second
static const int logicUpdateFrequency = 30; // How many times per second to update logic
static const int ticksPerLogicFrame = ticksPerSecond / logicUpdateFrequency; // How many ticks to a frame
static const float dt = 1.f / logicUpdateFrequency; // The elapsed game time a logic update takes
int elapsedTicks; // How long in ticks an iteration of the main game loop takes
int previousTime, currentTime = 0; // Time trackers
int bukkit = 0; // The accumulator (lol)

bool running = true;
while ( running ) {
// Event Handling
// .
// .
// .

// Logic
previousTime = currentTime;
currentTime = SDL_GetTicks();
elapsedTicks = currentTime - previousTime;

for ( bukkit += elapsedTicks; bukkit >= ticksPerLogicFrame; bukkit -= ticksPerLogicFrame )
update( dt );

// Rendering
render( static_cast<float>( bukkit ) / ticksPerLogicFrame ); // range from [0 1), 0 being the previous state, 1 being the current state
}





And a sample entity might do something like so:


class SampleEntity {
public:
void Update( float dt );
void Render( float interp );

private:
float oldAlpha, currentAlpha;
Vector2 oldPos, currentPos;
Vector2 heading;
};

void SampleEntity::Update( float dt ) {
// Set this entity's previous state
oldPos = currentPos;
oldAlpha = currentAlpha;

// Do some wandering
heading += Vector2( rand()%10 - 5, rand()%10 - 5 );
if ( heading.x || heading.y )
heading.normalize();

// Set this entity's current state
currentPos += heading * dt;
currentAlpha = sin( gCurrentTime * 180 / twopi ) * 127 + 128;
}

void SampleEntity::Render( float interp ) {
Vector2 finalPos = (currentPos - oldPos) * interp + oldPos;
float finalAlpha = (currentAlpha - oldAlpha) * interp + oldAlpha;

myRenderer->DrawFoobar( finalPos, finalAlpha );
}

Share this post


Link to post
Share on other sites
Quote:
Original post by _fastcall
"Fix Your Timestep" doesn't apply exclusively for intense physics games, it applies to any game that wishes to ensure that the simulations are accurate across multiple machines of varying speeds while achieving high frame rates at the same time. For instance, the setup you have right now, won't run smoothly if you set the logic update frequency to update once per second, even though you're rendering the same state more than a thousand times per second. If you interpolate between the two states, you get the appearance of running the game at thousands of frames per second, even though your logic is only being updated once per second. Here's a setup I used for the pong game:

*** Source Snippet Removed ***

And a sample entity might do something like so:

*** Source Snippet Removed ***


I was getting crazy around this subject, even after reading "Fix your Time Step".
Your explanation made everything clear.

Thanks!

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

Sign in to follow this