PDA

View Full Version : Graphical Tearing Question...



Xadeu2005
01-24-2005, 06:52 PM
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 (http://members.cox.net/nick.ueda/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(&amp;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 *) &amp;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 *) &amp;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 *) &amp;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, &amp;pfd);
SetPixelFormat(hDC, pixelFormat, &amp;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, &amp;ps);
EndPaint(hWnd, &amp;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(&amp;windowClass))
return 0;

if (fullscreen) // fullscreen?
{
DEVMODE dmScreenSettings; // device mode
memset(&amp;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_PELSWID TH|DM_PELSHEIGHT;

if (ChangeDisplaySettings(&amp;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(&amp;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 (&amp;msg, NULL, 0, 0, PM_NOREMOVE))
{
if (!GetMessage (&amp;msg, NULL, 0, 0))
{
exiting = true;
break;
}
TranslateMessage (&amp;msg);
DispatchMessage (&amp;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

mikael_aronsson
01-24-2005, 11:10 PM
Well, does it go away if you turn on vsync ?

ZbuffeR
01-25-2005, 12:53 AM
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);

Xadeu2005
01-25-2005, 02:36 PM
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

Omaha
01-29-2005, 10:09 PM
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?