Graphical Tearing Question...

Hello all,
I am writing a simple “Falling Block” (Tetris) game.

Currently I am testing a single block that falls from the top of the screen to the bottom of the screen. The user is allowed to move the piece all the way to the left of the “board” and all the way to the right of the “board” as the piece falls at a constant rate. If I constantly move the piece all the way left and all the way right. The polygon will eventually start to “tear” horizontally. It starts “tearing” at the bottom and continues upward until the piece is normal again. What causes this tearing and is there an effective way to prevent it from happening?

Monitor Refresh rate = 60 hz
Vsync is off
Target FPS should be exactly 60 fps

The tearing effect is really slow (and obvious) and happens every 15 seconds or so, which leads me to believe that I am very close to hitting 60 fps but not quite.

Here is a link to the executable…
Block.rar

All of my code is listed below and was compiled in Dev-C++.

Any help would be greatly appreciated,
Xadeu2005

//winmain.cpp
#include <stdlib.h>
#include <windows.h>										// Header File For Windows
#include <stdio.h>											// Header File For Standard Input / Output
#include <stdarg.h>		
#include <windows.h>
#include <gl/gl.h>
#include <gl/glu.h>
#include "CGfxOpenGL.h"

bool exiting = false;
long windowWidth = 800;
long windowHeight = 600;
long windowBits = 32;
bool fullscreen = false;
int steps[6]={ 1, 2, 4, 5, 10, 20 };					// Stepping Values For Slow Video Adjustment
HDC hDC;

CGfxOpenGL *g_glRender = NULL;

struct			 											// Create A Structure For The Timer Information
{
  __int64       frequency;									// Timer Frequency
  float         resolution;									// Timer Resolution
  unsigned long mm_timer_start;								// Multimedia Timer Start Value
  unsigned long mm_timer_elapsed;							// Multimedia Timer Elapsed Time
  bool			performance_timer;							// Using The Performance Timer?
  __int64       performance_timer_start;					// Performance Timer Start Value
  __int64       performance_timer_elapsed;					// Performance Timer Elapsed Time
} timer;

void TimerInit(void)										// Initialize Our Timer (Get It Ready)
{
	memset(&timer, 0, sizeof(timer));						// Clear Our Timer Structure

	// Check To See If A Performance Counter Is Available
	// If One Is Available The Timer Frequency Will Be Updated
	if (!QueryPerformanceFrequency((LARGE_INTEGER *) &timer.frequency))
	{
		// No Performace Counter Available
		timer.performance_timer	= FALSE;					// Set Performance Timer To FALSE
		timer.mm_timer_start	= timeGetTime();			// Use timeGetTime() To Get Current Time
		timer.resolution		= 1.0f/1000.0f;				// Set Our Timer Resolution To .001f
		timer.frequency			= 1000;						// Set Our Timer Frequency To 1000
		timer.mm_timer_elapsed	= timer.mm_timer_start;		// Set The Elapsed Time To The Current Time
	}
	else
	{
		// Performance Counter Is Available, Use It Instead Of The Multimedia Timer
		// Get The Current Time And Store It In performance_timer_start
		QueryPerformanceCounter((LARGE_INTEGER *) &timer.performance_timer_start);
		timer.performance_timer			= TRUE;				// Set Performance Timer To TRUE
		// Calculate The Timer Resolution Using The Timer Frequency
		timer.resolution				= (float) (((double)1.0f)/((double)timer.frequency));
		// Set The Elapsed Time To The Current Time
		timer.performance_timer_elapsed	= timer.performance_timer_start;
	}
}

float TimerGetTime()										// Get Time In Milliseconds
{
	__int64 time;											// time Will Hold A 64 Bit Integer

	if (timer.performance_timer)							// Are We Using The Performance Timer?
	{
		QueryPerformanceCounter((LARGE_INTEGER *) &time);	// Grab The Current Performance Time
		// Return The Current Time Minus The Start Time Multiplied By The Resolution And 1000 (To Get MS)
		return ( (float) ( time - timer.performance_timer_start) * timer.resolution)*1000.0f;
	}
	else
	{
		// Return The Current Time Minus The Start Time Multiplied By The Resolution And 1000 (To Get MS)
		return( (float) ( timeGetTime() - timer.mm_timer_start) * timer.resolution)*1000.0f;
	}
}

void SetupPixelFormat(HDC hDC)
{
    int pixelFormat;

    PIXELFORMATDESCRIPTOR pfd =
    {   
        sizeof(PIXELFORMATDESCRIPTOR),  // size
            1,                          // version
            PFD_SUPPORT_OPENGL |        // OpenGL window
            PFD_DRAW_TO_WINDOW |        // render to window
            PFD_DOUBLEBUFFER,           // support double-buffering
            PFD_TYPE_RGBA,              // color type
            32,                         // prefered color depth
            0, 0, 0, 0, 0, 0,           // color bits (ignored)
            0,                          // no alpha buffer
            0,                          // alpha bits (ignored)
            0,                          // no accumulation buffer
            0, 0, 0, 0,                 // accum bits (ignored)
            16,                         // depth buffer
            0,                          // no stencil buffer
            0,                          // no auxiliary buffers
            PFD_MAIN_PLANE,             // main layer
            0,                          // reserved
            0, 0, 0,                    // no layer, visible, damage masks
    };

    pixelFormat = ChoosePixelFormat(hDC, &pfd);
    SetPixelFormat(hDC, pixelFormat, &pfd);
}

LRESULT CALLBACK MainWindowProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
    static HDC hDC;
    static HGLRC hRC;
    int height, width;
    int fwKeys;
    LPARAM keyData;
    fwKeys = (int)wParam;    // virtual-key code 
    keyData = lParam;          // key data 

    // dispatch messages
    switch (uMsg)
    {   
    case WM_CREATE:         // window creation
        hDC = GetDC(hWnd);
        SetupPixelFormat(hDC);
        //SetupPalette();
        hRC = wglCreateContext(hDC);
        wglMakeCurrent(hDC, hRC);
        break;

    case WM_DESTROY:            // window destroy
    case WM_QUIT:
    case WM_CLOSE:                  // windows is closing

        // deselect rendering context and delete it
        wglMakeCurrent(hDC, NULL);
        wglDeleteContext(hRC);

        // send WM_QUIT to message queue
        PostQuitMessage(0);
        break;

    case WM_SIZE:
        height = HIWORD(lParam);        // retrieve width and height
        width = LOWORD(lParam);

        g_glRender->SetupProjection(width, height);
        break;

    case WM_ACTIVATEAPP:        // activate app
        break;

    case WM_PAINT:              // paint
        PAINTSTRUCT ps;
        BeginPaint(hWnd, &ps);
        EndPaint(hWnd, &ps);
        break;

    case WM_LBUTTONDOWN:        // left mouse button
        break;

    case WM_RBUTTONDOWN:        // right mouse button
        break;

    case WM_MOUSEMOVE:          // mouse movement
        break;

    case WM_LBUTTONUP:          // left button release
        break;

    case WM_RBUTTONUP:          // right button release
        break;

    case WM_KEYUP:
        g_glRender->keys[fwKeys] = false;
        break;

    case WM_KEYDOWN:

        g_glRender->keys[fwKeys] = true;
        switch(fwKeys)
        {
        case VK_ESCAPE:
            PostQuitMessage(0);
            break;

        default:
            break;
        }

        break;

    default:
        break;
    }
    return DefWindowProc(hWnd, uMsg, wParam, lParam);
}

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nShowCmd)
{
    WNDCLASSEX windowClass;     // window class
    HWND       hwnd;            // window handle
    MSG        msg;             // message
    DWORD      dwExStyle;       // Window Extended Style
    DWORD      dwStyle;         // Window Style
    RECT       windowRect;

    g_glRender = new CGfxOpenGL;
    
    windowRect.left=(long)0;                        // Set Left Value To 0
    windowRect.right=(long)windowWidth; // Set Right Value To Requested Width
    windowRect.top=(long)0;                         // Set Top Value To 0
    windowRect.bottom=(long)windowHeight;   // Set Bottom Value To Requested Height

    // fill out the window class structure
    windowClass.cbSize          = sizeof(WNDCLASSEX);
    windowClass.style           = CS_HREDRAW | CS_VREDRAW;
    windowClass.lpfnWndProc     = MainWindowProc;
    windowClass.cbClsExtra      = 0;
    windowClass.cbWndExtra      = 0;
    windowClass.hInstance       = hInstance;
    windowClass.hIcon           = LoadIcon(NULL, IDI_APPLICATION);  // default icon
    windowClass.hCursor         = LoadCursor(NULL, IDC_ARROW);      // default arrow
    windowClass.hbrBackground   = NULL;                             // don't need background
    windowClass.lpszMenuName    = NULL;                             // no menu
    windowClass.lpszClassName   = "GLClass";
    windowClass.hIconSm         = LoadIcon(NULL, IDI_WINLOGO);      // windows logo small icon

    // register the windows class
    if (!RegisterClassEx(&windowClass))
        return 0;

    if (fullscreen)                             // fullscreen?
    {
        DEVMODE dmScreenSettings;                   // device mode
        memset(&dmScreenSettings,0,sizeof(dmScreenSettings));
        dmScreenSettings.dmSize = sizeof(dmScreenSettings); 
        dmScreenSettings.dmPelsWidth = windowWidth;         // screen width
        dmScreenSettings.dmPelsHeight = windowHeight;           // screen height
        dmScreenSettings.dmBitsPerPel = windowBits;             // bits per pixel
        dmScreenSettings.dmFields=DM_BITSPERPEL|DM_PELSWIDTH|DM_PELSHEIGHT;

        if (ChangeDisplaySettings(&dmScreenSettings, CDS_FULLSCREEN) != DISP_CHANGE_SUCCESSFUL)
        {
            // setting display mode failed, switch to windowed
            MessageBox(NULL, "Display mode failed", NULL, MB_OK);
            fullscreen = FALSE; 
        }
    }

    if (fullscreen)                             // Are We Still In Fullscreen Mode?
    {
        dwExStyle=WS_EX_APPWINDOW;                  // Window Extended Style
        dwStyle=WS_POPUP;                       // Windows Style
        ShowCursor(FALSE);                      // Hide Mouse Pointer
    }
    else
    {
        dwExStyle=WS_EX_APPWINDOW | WS_EX_WINDOWEDGE;   // Window Extended Style
        dwStyle=WS_OVERLAPPEDWINDOW;                    // Windows Style
    }

    AdjustWindowRectEx(&windowRect, dwStyle, FALSE, dwExStyle);     // Adjust Window To True Requested Size

    // class registered, so now create our window
    hwnd = CreateWindowEx(NULL,                                 // extended style
        "GLClass",                          // class name
        "Open GL",      // app name
        dwStyle | WS_CLIPCHILDREN |
        WS_CLIPSIBLINGS,
        0, 0,                               // x,y coordinate
        windowRect.right - windowRect.left,
        windowRect.bottom - windowRect.top, // width, height
        NULL,                               // handle to parent
        NULL,                               // handle to menu
        hInstance,                          // application instance
        NULL);                              // no extra params

    hDC = GetDC(hwnd);

    // check if window creation failed (hwnd would equal NULL)
    if (!hwnd)
        return 0;

    ShowWindow(hwnd, SW_SHOW);          // display the window
    UpdateWindow(hwnd);                 // update the window

    TimerInit();
    g_glRender->Init();

    while (!exiting)
    {
        float start = TimerGetTime();

        if (g_glRender->TurningBlocked == true)
        {   
           //When the Turning was blocked
           if (g_glRender->TimeTurnFeatureBlocked == 0.0f)
           {
                g_glRender->TimeTurnFeatureBlocked = TimerGetTime();
           }
           //Check to see if the Turning has been blocked for 250 ms
           if ((TimerGetTime() - g_glRender->TimeTurnFeatureBlocked) >= 250)
           {
              g_glRender->TimeTurnFeatureBlocked = 0.0f;
              g_glRender->TurningBlocked = false;
           }
        }

        if (g_glRender->MovingBlocked == true)
        {
           //When the Moving was blocked
           if (g_glRender->TimeMoveFeatureBlocked == 0.0f)
           {
                g_glRender->TimeMoveFeatureBlocked = TimerGetTime();
           }
           //Check to see if the moving has been blocked for 250 ms
           if ((TimerGetTime() - g_glRender->TimeMoveFeatureBlocked) >= 150)
           {
              g_glRender->TimeMoveFeatureBlocked = 0.0f;
              g_glRender->MovingBlocked = false;
           }
        }
        
        g_glRender->Prepare(0.0f);
        g_glRender->Render();
        SwapBuffers(hDC);

        while(TimerGetTime() < start + 16.6667) {} //should restrict MAX FPS to 60
                                                //only update every 16.6667 ms
        while (PeekMessage (&msg, NULL, 0, 0, PM_NOREMOVE))
        {
            if (!GetMessage (&msg, NULL, 0, 0))
            {
                exiting = true;
                break;
            }
            TranslateMessage (&msg);
            DispatchMessage (&msg);
        }
    }

    delete g_glRender;
	
    if (fullscreen)
    {
        ChangeDisplaySettings(NULL,0);          // If So Switch Back To The Desktop
        ShowCursor(TRUE);                       // Show Mouse Pointer
    }

    return (int)msg.wParam;
}
//CGfxOpenGL.h
#ifndef __GL_COMPONENT
#define __GL_COMPONENT

#define NUMPAD_8 104
#define NUMPAD_9 105
#define RIGHT_ARROW 39
#define LEFT_ARROW 37
#define DOWN_ARROW 40
class CGfxOpenGL
{
private:
	int m_windowWidth;
	int m_windowHeight;
    float trans_amount;
    float rot_amount;
    float angle_start;
	float m_angle;
    float x_start;
    float y_start;
    bool TurnClockwise;
    bool TurnCounterClockwise;
public:
    bool TurningBlocked;
    int TurnCount;
    float TimeTurnFeatureBlocked;
    
    bool MoveRight;
    bool MoveLeft;
    bool MovingBlocked;
    int MoveCount;
    float TimeMoveFeatureBlocked;


    bool keys[256];
	CGfxOpenGL();
	virtual ~CGfxOpenGL();

	bool Init();
	bool Shutdown();

	void SetupProjection(int width, int height);

	void Prepare(float dt);
	void Render();
	void RenderBoard();
    void RenderSquare();
};

#endif
//CGfxOpenGL.cpp
#ifdef _WINDOWS
#include <windows.h>
#endif

#include <gl/gl.h>
#include <gl/glu.h>
#include <math.h>
#include "CGfxOpenGL.h"
// disable implicit float-double casting
#pragma warning(disable:4305)

CGfxOpenGL::CGfxOpenGL()
{
  MoveRight = false;
  MoveLeft = false;
  MovingBlocked = false;
  MoveCount = 0;
  TimeMoveFeatureBlocked = 0.0f;
  
  TurnClockwise = false;
  TurnCounterClockwise = false;
  TurningBlocked = false;
  TurnCount = 0;
  TimeTurnFeatureBlocked = 0.0f;
}

CGfxOpenGL::~CGfxOpenGL()
{
}

bool CGfxOpenGL::Init()
{   
    // clear to black background
    glClearColor(0.0, 0.0, 0.0, 0.0);
    x_start = 15.5f;
    y_start = 20.5;
//    x_start = 16.0f;
//   y_start = 21.0f;
    trans_amount = 0.0f;
    angle_start = 0.0f;
    rot_amount = 10.0f;
    return true;
}

bool CGfxOpenGL::Shutdown()
{
    return true;
}

void CGfxOpenGL::SetupProjection(int width, int height)
{
    if (height == 0)                    // don't want a divide by zero
    {
        height = 1;                 
    }

    glViewport(0, 0, width, height);        // reset the viewport to new dimensions
    glMatrixMode(GL_PROJECTION);            // set projection matrix current matrix
    glLoadIdentity();                       // reset projection matrix

    // calculate aspect ratio of window
    //gluPerspective(52.0f,(GLfloat)width/(GLfloat)height,1.0f,1000.0f);
    glOrtho(0,30,0,20,-10,10);
    glMatrixMode(GL_MODELVIEW);             // set modelview matrix
    glLoadIdentity();                       // reset modelview matrix
    m_windowWidth = width;
    m_windowHeight = height;
}

void CGfxOpenGL::Prepare(float dt)
{
     //Logic for moving left.
    if (MoveLeft == false)
    {
         if (MovingBlocked == false)
         {
               MoveLeft = keys[LEFT_ARROW] == true;
               MovingBlocked = MoveLeft == true;
         }
    }
    else
    {
       if (x_start > 10.5f)
       {
         trans_amount = -0.1f;
         x_start = x_start + trans_amount;
       }
       MoveCount++;
       if (MoveCount == 10)
       {
          MoveCount = 0;
          MoveLeft = false;
       }
    }
     //Logic for moving right.
    if (MoveRight == false)
    {
         if (MovingBlocked == false)
         {
               MoveRight = keys[RIGHT_ARROW] == true;
               MovingBlocked = MoveRight == true;
         }
    }
    else
    {
       if (x_start < 19.5f)
       {
         trans_amount = 0.1;
         x_start = x_start + trans_amount;
       }
       MoveCount++;
       if (MoveCount == 10)
       {
          MoveCount = 0;
          MoveRight = false;
       }
    }
    //Logic for Turning Clockwise
    if (TurnClockwise == false)
    {
         if (TurningBlocked == false)
         {
             TurnClockwise = keys[NUMPAD_9] == true;
             TurningBlocked = TurnClockwise == true;
         }
    }
    else
    {
            //Do rotation calculations
            rot_amount = -30.0f;
            angle_start = angle_start + rot_amount;
            //Increment the turn counter by 1
            TurnCount++;
            if (TurnCount == 3)
            {
             TurnCount = 0;
             TurnClockwise = false;
            }
    }

    //Logic for Turning CounterClockwise
    if (TurnCounterClockwise == false)
    {
         if (TurningBlocked == false)
         {
             TurnCounterClockwise = keys[NUMPAD_8] == true;
             TurningBlocked = TurnCounterClockwise == true;
         }
    }
    else
    {
            //Do rotation calculations
            rot_amount = 30.0f;
            angle_start = angle_start + rot_amount;
            //Increment the turn counter by 1
            TurnCount++;
            if (TurnCount == 3)
            {
             TurnCount = 0;
             TurnCounterClockwise = false;
            }
    }
    
    if (keys[DOWN_ARROW] == true)
    {
        y_start = y_start - 0.5;   
    }
    else
    {
        y_start = y_start - 0.011111;    
    }

}

void CGfxOpenGL::Render()
{
    // clear screen and depth buffer
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);     
    glLoadIdentity();

    RenderBoard();

    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();

    if ((ceil(y_start-0.5)) == 0.0) y_start = 20.5;

    glTranslatef(x_start,y_start,0.0);
    glRotatef(angle_start,0.0,0.0,1.0);
    RenderSquare();
}

void CGfxOpenGL::RenderBoard()
{
    glBegin(GL_LINES);
       glVertex3f(10.0,0.0,0.0);
       glVertex3f(20.0,0.0,0.0);
    glEnd();
    
    glBegin(GL_LINES);
       glVertex3f(10.0,0.0,0.0);
       glVertex3f(10.0,20.0,0.0);
       glVertex3f(20.0,0.0,0.0);
       glVertex3f(20.0,20.0,0.0);
    glEnd();
}

void CGfxOpenGL::RenderSquare()
{
     glRectf(-0.5,-0.5,0.5,0.5);
}

Thanks for any help,
Xadeu2005

Well, does it go away if you turn on vsync ?

Like mikael_aronsson said,

// turn on vsync
wglSwapInterval(1);

In fact the monitor refresh rate is not exactly 60.00000000Hz (and probaly the same for your fps). The only way to avoid tearing is the ask the card to swap buffers right after a vblank, and wglSwapInterval does this.

And just in case :
// turn off vsync
wglSwapInterval(0);

Thanks mikael_aronsson and ZBuffer,
That did the trick…
What really confuses me now is that before I tried your solution, I tried turning “wait for vsync” on in my driver properties and it didn’t get rid of the tearing, but the extension did work…why did this happen?

Thanks,
Nick

Some drivers have a setting that allow application settings to override driver settings. Maybe you’ve got one of those?

It’s like how some STD’s cause a burning a fierce. Hmm… maybe I’ve got one of those?