Vsync, double buffering, and smooth animation

Started by
23 comments, last by Extrarius 15 years, 10 months ago
Hey guys, I was just wondering if anyone can explain to me exactly how these things all work. I understand how vsync works, and it's disadvantages. But I hear of people who disable it, but I don't see how that is possible. In my application I have a white square moving around on a black background, and it looks horrible. Is that just because of the simplicity of the scene that the tearing is so apparent? Or is there a way to pull off less tearing without vsync? I've also noticed that no other programs really have these problems. With vsync on or off. I'd just like to hear some of your strategies to reduce tearing. Thanks.
Advertisement
I always use Double Buffering!
In my game it makes just a small difference when I disable VSync.
Quote:I understand how vsync works, and it's disadvantages. But I hear of people who disable it, but I don't see how that is possible.
I don't know what you are using to draw to the screen, but I use SDL and OpenGL and can enable/disable VSync like that:
SDL_GL_SetAttribute ( SDL_GL_SWAP_CONTROL, 0); /* Disable VSync */SDL_GL_SetAttribute ( SDL_GL_SWAP_CONTROL, 1); /* Enable VSync */SDL_GL_SetAttribute ( SDL_GL_DOUBLEBUFFER, 0 ); /* Disable Double Buffering */SDL_GL_SetAttribute ( SDL_GL_DOUBLEBUFFER, 1 ); /* EnableDouble Buffering */
Quote:Original post by darknebula42
In my application I have a white square moving around on a black background, and it looks horrible.

Can we see the animation code?
With double-buffering enabled, tearing is almost non-existant on non-Vsync'ed applications.
Tearing is very common when we're not Vsync'ed (we're either too slow or too fast) and we're writting directly to the front buffer (that is, double-buffering disabled). Therefore any update is instantly showed to the monitor even if the whole scene rendering isn't complete

Terminology disgregation:
Some (i.e. DirectX) refer to Triple buffering to what other say it's Double Buffering.
Following DX concepts (I don't know about OGL) Single-Buffering is the usage of just one front buffer.
Double-Buffering is the usage of two buffers: The back buffer, where the scene is rendered to, and the front buffer, which is updated by copying the contents of the back-buffer. Even if Vsync is disabled, the front buffer is not shown until the whole copy isn't finished. Even if it is shown, just copying a buffer from video memory to video memory is very fast, thus tearing only happens on very rare situations with insanely low framerates.
Triple-Buffering uses three buffers: Front buffer, and two (hence this is why some call it double-buffering, assuming the front buffer will be always present) back buffers. When the first back buffer is used, and we're ready to render a new frame (the CPU is in this case much faster than the GPU) it uses the second one.
Disadvantage: This increases the delay between user input (i.e. pressing a key) and showing the results in screen by 2 frames. But rarely it is noted. Consumes more VRAM.
Advantage: If the third or fourth frame will need much more processing power (in which a double-buffered would suffer FPS drops) the CPU will have more time available to complete the task and avoid the performance decrease. In other words, light frames compensate the heavy ones. (Saying it easy: performance boost)

Advantages of Vsync:
*Removes completely tearing
*On fast machines, ensures that the game is running at the desired speed in all of them. (We can though, develop our own fps limiter, but it won't prevent tearing)
*Saves energy on laptops and heat (including desktop PCs) unless we're at low framerates. (Because otherwise the GPU is idle when waiting)
*It is better to leave it on in high-end GPUs

Disadvantages of Vsync:
*Since the system waits to render the screen, (when we're at low fps) we may decrease performance even more. This is why many games disables them, or include the option at least.
*Leave VSync off in low-end GPUs. (argueable)

Phew.... It's long. I hope you find this usefull. I'm not done though. But I'm afraid I'm out of time. Good luck!

Dark Sylinc

Edit: Fixed some typo errors
Quote:Original post by darknebula42
With vsync on or off. I'd just like to hear some of your strategies to reduce tearing. Thanks.

You kept me thinking about it. I'm confused. Are you sure you understand what tearing is? Because I don't see a game or hardware tearing since the 16-bit console era.
If you do, are you developing for PDAs, cellphones, GameBoy?

Strategies:
*Use double-buffering
*Use VSync
*Don't make a game that needs more processing power than the targeted device is capable of.
*Don't allow high monitors refresh-rate (some users may hate you)
*Use display lists on a device capable of doing vector graphics. I don't know if such gpu hardware exists nowadays, and better yet, is in the mass market.
*Decrease gfx detail when running at low fps.
Sorry. I'm using SDL, and OpenGL.

Here is my drawing:

c is a clock which measures the time it takes for the loop to finish
void MainMenu::logic(){	if(game->win.keys.isDown(SDLK_ESCAPE))		game->stateStack.pop();        // find the time passed to interpolate position	if(game->win.keys.isDown(SDLK_RIGHT))		x += c.stop() * 0.2;	if(game->win.keys.isDown(SDLK_LEFT))		x -= c.stop() * 0.2;	c.reset(); // reset the clock to zero}void MainMenu::draw(){	glLoadIdentity();	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);	glTranslatef(x, y, 0);	gl::Rectangle(100, 100).draw();        // Is this good practice here? :	glFlush();	SDL_GL_SwapBuffers();	glFinish();}


Here is my main loop:
time::Clock loopClock;const unsigned int defaultWaitTime = 14;unsigned int waitTime = defaultWaitTime;while(win.isOpen()){	time::sleep(waitTime);	loopClock.reset();	        // ...	game.stateStack.logic();	// ...	game.stateStack.draw();	if(loopClock.stop() < defaultWaitTime)		waitTime = defaultWaitTime - loopClock.check();}


These are in use:
SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);
SDL_GL_SetAttribute(SDL_GL_SWAP_CONTROL, 1);

Which enables Vsync and DoubleBuffering for OpenGL.
Thanks for all your help so far everyone!

With the code above, anyone who can't use VSync, it looks horrible and there is tearing. It looks like the sides of the rectangle are alive even. The sides of the rectangle are jerking? And the tops aren't that bad. Even on a Vsync enabled video card the movement isn't too amazing either. I could give out an exe, but I only have a linux binary available, no Windows exe, but if anyone really wants me to I could make one.
Your time calculations in main loop are incorrect - if that is all of the code.
Variable waitTime gets wrong value when that "if" is not executed. There should be "waitTime = defaultWaitTime" in else branch.

That may affect some timing things - but shouldn't do the behavior you describe...

Also that drawing might be a bit illogical, this order might be a slightly better:
glFlush();glFinish();SDL_GL_SwapBuffers();
(although, when I checked my own code, I didn't even have that glFinish)

Also, just to make sure, you are calling those functions only once per frame?
Because in code you presented there is a possibility to have two MainMenu objects in game.stateStack, which would draw the screen twice per "frame" which would create very weird visual effects.


Edit:
And now I think I am wrong with that timing issue, actually you shouldn't wait at all if rendering time was longer than defaultWaitTime.
Also please note that when you call that time::sleep (which probably calls SDL_Delay) your app might sleep much longer than the specified time.
If you are calling SDL_GL_SwapBuffers(), you shouldn't call glFlush() or glFinish(). Also, even if you weren't calling SDL_GL_SwapBuffers(), there's never a need to call both. See here for more info.
I forgot to mention that if I disable a constant FPS, it still looks like it's tearing. I'm certain it's not drawing one than more main menu at a time. And when I add the else to make sure the wait time is zero it looks smooth but at a certain time interval it shakes. It's smooth for about a second, then skips, smooth for a second, then skips, etc. Thanks again!

edit:
It's strange I can move the square for a certain period of time, stop, and start moving again and get into a rhythm where the square movements looks fine! That should give some clues. (this is when I still have the constant frame rate at about 59 fps), when the sleep is turned off though, the tearing increases but I don't get those weird "jumps".

edit2: to clarify, it looks as if the square as it's moving forward about every second is jumping a small distance backwards and being forward at the same time.

[Edited by - darknebula42 on June 22, 2008 3:09:28 PM]
Quote:Original post by Matias GoldbergEntire post

With all respect, your post is full of wrong statements. You can't write directly to the front buffer, so you can't disable double-buffering at all (unless, like you pointed out, by double buffering you mean triple buffering). So saying "any update is instantly showed to the monitor" is rediculous. If that was possible, you would be able to see the scene being rendered piece by piece when playing a game on a slow machine, which you don't, courtesy of double buffering.

Quote:With double-buffering enabled, tearing is almost non-existant on non-Vsync'ed applications.

Since you can't disable double buffering, I will assume you mean triple buffering, in which case this statement is also false, since the only advantage of triple buffering is the one you described in your post, and not resolving the vsync issue.

Quote:Even if Vsync is disabled, the front buffer is not shown until the whole copy isn't finished. Even if it is shown, just copying a buffer from video memory to video memory is very fast, thus tearing only happens on very rare situations with insanely low framerates.

I don't think you really understand why tearing happens. Let me briefly explain. The graphics card keeps feeding the contents of the front buffer to the monitor from top left to bottom right. Now, you can't write directly to the front buffer, so all your rendering happens on the back buffer. When you finish rendering your scene to the back buffer, you call Present(), telling the graphics card that you want it to send the contents of this back buffer of yours to the screen. But the card can only send the contents of the FRONT buffer to the screen. Both buffers are in video memory, so the card has to do one of two things. It has to either copy the contents of your back buffer to the front buffer (D3DSWAPEFFECT_COPY), or flip (swap) the buffers (D3DSWAPEFFECT_FLIP).

The first (copying) needs no explanation, but you should think about it as an atomic operation. Flipping is much like a pointer swap - the card will stop sending data to the monitor from the front buffer and start sending it from your back buffer, thus making it the new front buffer. It will modify your back buffer pointer to point to the old front buffer, so obviously the back buffer will contain the contents of the old front buffer (this is sometimes exploited when implementing fake motion blur effects and any other technique which requires access to the previous frame).

"Ok, but where does tearing come from?" I hear you say. Well, recall that the card constantly sends the contents of the front buffer to the screen from top left to bottom right. Now when you call Present() with vsync disabled, the contents of the front buffer will change (either due to the contents of the back buffer being copied over it or the card switching buffers). This change happens regardless of how much of the old front buffer has been sent to the card, and for the rest of the screen update, the pixels that the card will send will be from the new front buffer contents, causing the upper half of the screen to display pixels from the old frame and the lower half to display pixels from the new frame. In fact, if the card is fast enough of the scene is simple enough, this change in the contents of the front buffer can happen more than once during a screen update, causing the screen to display parts of more than just two images. Try writing an application that simply clears the back buffer to a different color each frame and presents it with vsync disabled and you will notice at least 4 or 5 tears!

If vsync is enabled, the card will not copy or flip buffers until the screen finishes updating and the entire contents of the front buffer have been sent to the monitor. Then the copy or swap happens before the next screen update happens, so the screen never shows more than one image at a time.

Oh crap, I got carried away again. Anyway, hope this helps.

This topic is closed to new replies.

Advertisement