Windows GDI question???

Started by
18 comments, last by JIMbond21 20 years, 7 months ago
He is speaking about tearing, not flickering. Tearing is when you are using double buffering, but are not updating the screen buffer in synch with the retrace, so you momentarily see one half of both frames (for only one retrace, until it happens again). Because of the nature of the retrace, it is most noticeable during horizontal movement - the faster, the more noticeable. Flickering, on the other hand, is when you erase and redraw. This is what will happen if he has a background brush set and is erasing the background every time. It causes an on then off effect, like a flickering light.

Jason Doucette | Matthew Doucette
Programming Windows, 5th Edition by Charles Petzold, Errata Addendum
projects / games | real-time graphics | artificial intelligence | world records | wallpapers / desktops / backgrounds
"Great minds discuss ideas, average minds discuss events, small minds discuss people." - Anna Eleanor Roosevelt, 1884-1962
Jason Doucette / Xona.comDuality: ZF — Xbox 360 classic arcade shmup featuring Dual Play
Advertisement
an easy way to get rid of most tearing-related artifacts for GDI drawing is to not blit the entire back buffer in one go. instead, only blit those rectangles which have been updated since the last redraw - the dirty rectangle concept. as long as those rectangles are small, i.e., sprite-related, most of the time you will not notice the update. on the other hand, if you have lots of sprites moving about, it can get a bit complicated to figure out what rects need to be updated, and it also can cause the updates to get out of sync with each other on slow boxes. so, if you can''t find a way around your problem using standard old-school GDI drawing optimizations, then try taking a look at the article at http://www.codeproject.com/gdi/TearingFreeDrawing.asp . iirc, the author used DDraw to gain access to the vsync, but the actual drawing was done via GDI.
Some one wanted the source code, this is for my game, it's still under development, as you can see.

//  Name: Ultra Pong//  Description: A pong clone where the player hits a ball trying to keep the ball from missing the paddle.//  Author: John Petersen//  Version: 1.0// Standard windows header file#include <windows.h>// Standard library#include <stdlib.h>void UpdateBuffer();                            // Updates the double buffervoid ClearBuffer();                             // Clears double buffervoid DisplaySplashScreen();                     // Displays splash screenint GameInit();                                 // Initializationint GameLoop();                                 // Main game loopint GameShutdown();                             // Shutdownint GetStringLength( char* String );            // Gets the length of a string// Draws textvoid DrawText( HDC hdc, char* szText, char* szFont, int Size, COLORREF crColor, bool bBold, bool bItalic, bool bUnderline, int x, int y);// Draws bitmapsvoid DrawBitmap(HDC hdc, HBITMAP hbmBitmap, HBITMAP hbmMask, int PosX, int PosY);// Creates a bitmap maskHBITMAP CreateBitmapMask( HBITMAP hbmBitmap, COLORREF crTransparent );const char GameTitle[] = "Ultra Pong";          // Constant string to the game's titleHWND GameWindow = NULL;                         // Handle to the game's windowHINSTANCE GameInstance;                         // Handle to the game's instanceHDC g_hDC;                                      // Global HDCRECT g_rcClientRect;                            // Stores the RECT of GamwWindowHDC g_hdcBuffer;                                // DC to the double bufferHBITMAP g_hbmBuffer = NULL;                     // HBITMAP that is copied from the double buffer to the global DCHBITMAP g_hbmOldBuffer = NULL;                  // Stores the old HBITAMP that was in the Buffer DCbool g_GameDone = false;                        // BOOL that is true if the game is doneHBITMAP g_hbmBall = NULL;                       // HBITMAP for the ballHBITMAP g_hbmBallMask = NULL;                   // HBITMAP for the ball maskHBITMAP g_hbmPaddle = NULL;                     // HBITMAP for the paddleHBITMAP g_hbmPaddleMask = NULL;                 // HBITMAP for the paddle maskHBITMAP g_hbmSplashScreen = NULL;               // HBITMAP for the splash screenHBITMAP g_hbmWinScreen = NULL;                  // HBITMAP for the win screenHBITMAP g_hbmLoseScreen = NULL;                 // HBITMAP for the lose screenint g_Score = 0;                                // Score for gamechar g_ScoreString[20];                         // String for scoreint g_BallsLeft = 5;                            // Number of balls leftchar g_BallsLeftString[10];                     // String for balls left    LONGLONG lastcount;    LONGLONG newcount;    LONGLONG ticknum;    LONGLONG tickdiff = 0;    int fps = 60;int round(double x){    return int(x + 0.5);}typedef struct _BALL{    int x, y;    int width, height;    int speedx, speedy;}BALL;typedef struct _PADDLE{    int x, y;    int width, height;    int speedx, speedy;}PADDLE;BALL g_Ball;PADDLE g_Paddle;// Update double buffervoid UpdateBuffer(){    BitBlt(g_hDC, 0, 0, g_rcClientRect.right, g_rcClientRect.bottom, g_hdcBuffer, 0, 0, SRCCOPY);}// Clears double buffervoid ClearBuffer(){    FillRect(g_hdcBuffer, &g_rcClientRect, (HBRUSH)GetStockObject(BLACK_BRUSH));}// Gets the length of a stringint GetStringLength( char* String ){    int Length;    for(Length = 0; *String; String++, Length++);    return Length;}// Creates a bitmap maskHBITMAP CreateBitmapMask( HBITMAP hbmBitmap, COLORREF crTransparent ){    HDC hdcMem, hdcMem2;    HBITMAP hbmMask;    BITMAP bitmap;    GetObject(hbmBitmap, sizeof(BITMAP), &bitmap);    hbmMask = CreateBitmap(bitmap.bmWidth, bitmap.bmHeight, 1, 1, NULL);    hdcMem = CreateCompatibleDC(0);    hdcMem2 = CreateCompatibleDC(0);    SelectObject(hdcMem, hbmBitmap);    SelectObject(hdcMem2, hbmMask);    COLORREF crOld = SetBkColor(hdcMem, crTransparent);    BitBlt(hdcMem2, 0, 0, bitmap.bmWidth, bitmap.bmHeight, hdcMem, 0, 0, SRCCOPY);    BitBlt(hdcMem, 0, 0, bitmap.bmWidth, bitmap.bmHeight, hdcMem2, 0, 0, SRCINVERT);    SetBkColor(hdcMem, crOld);    DeleteDC(hdcMem);    DeleteDC(hdcMem2);    return hbmMask;}// Draws bitmapsvoid DrawBitmap(HDC hdc, HBITMAP hbmBitmap, HBITMAP hbmMask, int PosX, int PosY){    BITMAP bm;	HDC hdcMem = CreateCompatibleDC(hdc);	HBITMAP hbmOld = (HBITMAP)SelectObject(hdcMem, hbmMask);    GetObject(hbmBitmap, sizeof(BITMAP), &bm);	BitBlt(hdc, PosX, PosY, bm.bmWidth, bm.bmHeight, hdcMem, 0, 0, SRCAND);	SelectObject(hdcMem, hbmBitmap);	BitBlt(hdc, PosX, PosY, bm.bmWidth, bm.bmHeight, hdcMem, 0, 0, SRCPAINT);	SelectObject(hdcMem, hbmOld);	DeleteDC(hdcMem);}// Draws textvoid DrawText( HDC hdc, char* szText, char* szFont, int Size, COLORREF crColor, bool bBold, bool bItalic, bool bUnderline, int x, int y){    HFONT hfFont;    long lfHeight;    HDC hdc2 = GetDC(NULL);    lfHeight = -MulDiv(Size, GetDeviceCaps(hdc2, LOGPIXELSY), 72);    ReleaseDC(NULL, hdc2);    hfFont = CreateFont(lfHeight, 0, 0, 0, 700 * bBold, bItalic, bUnderline, 0, 0, 0, 0, 0, 0, szFont);    HFONT hfFontOld = (HFONT)SelectObject(hdc, hfFont);    SetBkColor(hdc, RGB(255,255,255));    SetTextColor(hdc, crColor);    SetBkMode(hdc, TRANSPARENT);    TextOut(hdc, x, y, szText, GetStringLength(szText));    SetTextColor(hdc, RGB(0,0,0));    SelectObject(hdc, hfFontOld);    DeleteObject(hfFont);}// Displays splash screenvoid DisplaySplashScreen(){    DrawBitmap(g_hdcBuffer, g_hbmSplashScreen, NULL, (g_rcClientRect.right - 800) / 2, (g_rcClientRect.bottom - 600) / 2);    UpdateBuffer();    Sleep(2000);}// Displays win screenvoid DisplayWinScreen(){    DrawBitmap(g_hdcBuffer, g_hbmWinScreen, NULL, (g_rcClientRect.right - 400) / 2, (g_rcClientRect.bottom - 300) / 2);    UpdateBuffer();    Sleep(2000);}// Displays lose screenvoid DisplayLoseScreen(){    DrawBitmap(g_hdcBuffer, g_hbmLoseScreen, NULL, (g_rcClientRect.right - 400) / 2, (g_rcClientRect.bottom - 300) / 2);    UpdateBuffer();    g_Score = 0;    g_BallsLeft = 5;    Sleep(3000);}// Initializationint GameInit(){    if(!QueryPerformanceFrequency((LARGE_INTEGER*)&ticknum)){        MessageBox(GameWindow, "High Performance Timers Not Supported", "Error", MB_OK | MB_ICONEXCLAMATION);        return 0;	}    ShowCursor(false);    g_Ball.x = 430;    g_Ball.y = 200;    g_Ball.speedx = 6;    g_Ball.speedy = -6;    g_Paddle.x = 400;    g_Paddle.y = 600;    g_Paddle.speedx = 8;    g_hbmBall = (HBITMAP)LoadImage(NULL,"PongBall.bmp",IMAGE_BITMAP,0,0,LR_LOADFROMFILE);    g_hbmBallMask = CreateBitmapMask(g_hbmBall, RGB(255,255,255));    g_hbmPaddle = (HBITMAP)LoadImage(NULL,"PongPaddle.bmp",IMAGE_BITMAP,0,0,LR_LOADFROMFILE);    g_hbmPaddleMask = CreateBitmapMask(g_hbmPaddle, RGB(255,255,255));    g_hbmSplashScreen = (HBITMAP)LoadImage(NULL,"UltraPongSplashScreen.bmp",IMAGE_BITMAP,0,0,LR_LOADFROMFILE);    g_hbmWinScreen = (HBITMAP)LoadImage(NULL,"UltraPongYouWin.bmp",IMAGE_BITMAP,0,0,LR_LOADFROMFILE);    g_hbmLoseScreen = (HBITMAP)LoadImage(NULL,"UltraPongYouLose.bmp",IMAGE_BITMAP,0,0,LR_LOADFROMFILE);    g_hDC = GetDC(GameWindow);    GetClientRect(GameWindow, &g_rcClientRect);    g_hdcBuffer = CreateCompatibleDC(g_hDC);    g_hbmBuffer = CreateCompatibleBitmap(g_hDC, g_rcClientRect.right, g_rcClientRect.bottom);    g_hbmOldBuffer = (HBITMAP)SelectObject(g_hdcBuffer, g_hbmBuffer);	FillRect(g_hdcBuffer, &g_rcClientRect, (HBRUSH)GetStockObject(BLACK_BRUSH));    UpdateBuffer();    DisplaySplashScreen();    while(!(GetAsyncKeyState(VK_SPACE) & 0x8000))    {        ClearBuffer();        DrawText(g_hdcBuffer, "Press Space Bar to Start", "Comic Sans MS", 20, RGB(0,0,255), 1, 0, 0, ((g_rcClientRect.right - 300) / 2), ((g_rcClientRect.bottom - 30) / 2));        UpdateBuffer();    }    return 0;}// Main game loopint GameLoop(){    QueryPerformanceCounter((LARGE_INTEGER*)&lastcount);    ClearBuffer();    if((GetAsyncKeyState(VK_LBUTTON) & 0x8000) || (GetAsyncKeyState(VK_ESCAPE) & 0x8000))    {        g_GameDone = true;    }    if(GetAsyncKeyState(VK_RIGHT) & 0x8000)    {        if((g_Paddle.x + 128) < g_rcClientRect.right)        {            g_Paddle.x += g_Paddle.speedx;        }    }    if(GetAsyncKeyState(VK_LEFT) & 0x8000)    {        if(g_Paddle.x > g_rcClientRect.left)        {            g_Paddle.x -= g_Paddle.speedx;        }    }    if(g_Ball.x < g_rcClientRect.left)    {        g_Ball.speedx *= -1;    }    if((g_Ball.x + 24) > g_rcClientRect.right)    {        g_Ball.speedx *= -1;    }    if(((g_Ball.y + 24) > g_Paddle.y) && ((g_Ball.y + 24) < (g_Paddle.y + 4)) && ((g_Ball.x + 24) > g_Paddle.x) && (g_Ball.x < (g_Paddle.x + 128)))    {        g_Ball.speedy *= -1;        g_Score += 10;    }    if(g_Ball.y < g_rcClientRect.top)    {        g_Ball.speedy *= -1;    }    if(g_Ball.y > g_rcClientRect.bottom)    {        g_BallsLeft -= 1;        if(g_BallsLeft == 0)        {            DisplayLoseScreen();        }        g_Ball.x = rand()%g_rcClientRect.right;        g_Ball.y = 200;        g_Ball.speedx = 6;        g_Ball.speedy = -6;        while(!(GetAsyncKeyState(VK_SPACE) & 0x8000) && !g_GameDone)        {            ClearBuffer();            if((GetAsyncKeyState(VK_LBUTTON) & 0x8000) || (GetAsyncKeyState(VK_ESCAPE) & 0x8000))            {                g_GameDone = true;            }            if(GetAsyncKeyState(VK_RIGHT) & 0x8000)            {                if((g_Paddle.x + 128) < g_rcClientRect.right)                {                    g_Paddle.x += g_Paddle.speedx;                }            }            if(GetAsyncKeyState(VK_LEFT) & 0x8000)            {                if(g_Paddle.x > g_rcClientRect.left)                {                    g_Paddle.x -= g_Paddle.speedx;                }            }            wsprintf(g_ScoreString,"%d",g_Score);            wsprintf(g_BallsLeftString,"%d",g_BallsLeft);            DrawText(g_hdcBuffer,g_ScoreString,"Comic Sans MS",30,RGB(0,255,0),1,0,0,10,(g_rcClientRect.bottom - 60));            DrawText(g_hdcBuffer,g_BallsLeftString,"Comic Sans MS",30,RGB(0,255,0),1,0,0,(g_rcClientRect.right - 120),(g_rcClientRect.bottom - 60));            DrawText(g_hdcBuffer, "Press Space Bar to Lanch", "Comic Sans MS", 20, RGB(0,0,255), 1, 0, 0, ((g_rcClientRect.right - 320) / 2), ((g_rcClientRect.bottom - 30) / 2));            DrawBitmap(g_hdcBuffer,g_hbmPaddle,g_hbmPaddleMask,g_Paddle.x,g_Paddle.y);            UpdateBuffer();            QueryPerformanceCounter((LARGE_INTEGER*)&newcount);            tickdiff = newcount - lastcount;            lastcount = newcount;            while(round(ticknum/fps) >= tickdiff)            {            QueryPerformanceCounter((LARGE_INTEGER*)&newcount);            tickdiff += newcount - lastcount;            lastcount = newcount;            }        }    }    g_Ball.x += g_Ball.speedx;    g_Ball.y += g_Ball.speedy;    wsprintf(g_ScoreString,"%d",g_Score);    wsprintf(g_BallsLeftString,"%d",g_BallsLeft);    DrawText(g_hdcBuffer,g_ScoreString,"Comic Sans MS",30,RGB(0,255,0),1,0,0,10,(g_rcClientRect.bottom - 60));    DrawText(g_hdcBuffer,g_BallsLeftString,"Comic Sans MS",30,RGB(0,255,0),1,0,0,(g_rcClientRect.right - 120),(g_rcClientRect.bottom - 60));    DrawBitmap(g_hdcBuffer,g_hbmPaddle,g_hbmPaddleMask,g_Paddle.x,g_Paddle.y);    DrawBitmap(g_hdcBuffer,g_hbmBall,g_hbmBallMask,g_Ball.x,g_Ball.y);    UpdateBuffer();    QueryPerformanceCounter((LARGE_INTEGER*)&newcount);    tickdiff = newcount - lastcount;    lastcount = newcount;    while(round(ticknum/fps) >= tickdiff)    {        QueryPerformanceCounter((LARGE_INTEGER*)&newcount);        tickdiff += newcount - lastcount;        lastcount = newcount;    }    return 0;}// Shutdownint GameShutdown(){    SelectObject(g_hdcBuffer, g_hbmOldBuffer);	DeleteDC(g_hdcBuffer);	DeleteObject(g_hbmBuffer);    ReleaseDC(GameWindow, g_hDC);    return 0;}// Message handlerLRESULT CALLBACK GameProc( HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam ){    switch(msg)    {    case WM_CLOSE:		g_GameDone = true;		return 0;	case WM_DESTROY:		PostQuitMessage(0);		return 0;    default:        return DefWindowProc(hwnd, msg, wParam, lParam);    }}// WinMain function for the gameint APIENTRY WinMain( HINSTANCE p_instance, HINSTANCE p_prev_instance, LPSTR p_cmd_line, int p_show ){    MSG msg;	GameInstance = p_instance;	WNDCLASS GameWindowClass;	GameWindowClass.style				= CS_OWNDC;	GameWindowClass.cbClsExtra			= 0;	GameWindowClass.cbWndExtra			= 0;	GameWindowClass.hInstance			= GameInstance;	GameWindowClass.hIcon				= LoadIcon(NULL,IDI_WINLOGO);	GameWindowClass.hCursor		        = LoadCursor(NULL,IDC_ARROW);	GameWindowClass.hbrBackground		= (HBRUSH)GetStockObject(LTGRAY_BRUSH);	GameWindowClass.lpszMenuName		= NULL;	GameWindowClass.lpszClassName		= "Game Window Class";	GameWindowClass.lpfnWndProc         = GameProc;    if(!RegisterClass(&GameWindowClass)){        MessageBox(GameWindow, "Couldn't register class", "Error", MB_OK | MB_ICONEXCLAMATION);        return 0;	}    GameWindow = CreateWindow("Game Window Class",GameTitle,WS_POPUP | WS_MAXIMIZE | WS_VISIBLE,0,0,640,480,NULL,NULL,GameInstance,NULL);    GameInit();    while(!g_GameDone){        if(PeekMessage(&msg,NULL,0,0,PM_REMOVE)){			TranslateMessage(&msg);			DispatchMessage(&msg);		}        GameLoop();    }    if(!DestroyWindow(GameWindow)){        MessageBox(GameWindow, "Couldn't destroy window", "Error", MB_OK | MB_ICONEXCLAMATION);        return 0;	}	GameWindow = NULL;	if(!UnregisterClass("Game Window Class",GameInstance)){        MessageBox(GameWindow, "Couldn't unregister class", "Error", MB_OK | MB_ICONEXCLAMATION);        return 0;	}	return 0;}


sorry, im on dial up, putting the bitmaps up would take forever, but heres the source if anyone's interested.

[edited by - JIMbond21 on September 5, 2003 7:16:00 PM]
THX,JAP
Here is your Pong in a neat format...

PONG.txt

I would like to help solve your tearing problem in GDI since I'll be finishing a PacMan clone in GDI with Bitmaps also....

I wonder why you don't call your ClearBuffer and UpdateBuffer one time here:

while(!g_GameDone)
{

if(PeekMessage(&msg,NULL,0,0,PM_REMOVE))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}

ClearBuffer();
GameLoop();
UpdateBuffer();

}

This seems much easier to understand. I see it called a couple times. Why not clear it once, let GameLoop do all your drawing, then UpdateBuffer once, then wait to synch to 30 frames/second.

Your Sleep(2000) and Sleep(3000) may be causing the tearing? Not sure why that's necessary... Another thing I notice is you do DeleteDC each time in your DrawBitMap function. If I understand correctly, the DC for the Bitmaps can be left allocated throughout the game. The CreateCompatibleDC can be done in your GameInit (you need 3 mem DC allocated, two for paddles, one for ball), then three DeleteDC at your GameShutDown. I'm learning Win Bitmaps as well, and trying to figure out the best way to do this....with no tearing, flickering, flearing, shearing, or tickering

Phil P

[edited by - PhilVaz on September 5, 2003 8:51:16 PM]
I havent'' done much with bitmaps in GDI, but I will say that calling DestroyDC every time a bitmap is drawn can''t be good, right? On a small sub-note, I rememeber when I first programmed Pong, I created a brush EVERY TIME I drew something. As you can imagine, I ran out of memory VERY quickly

Sleep(2000) is quite long for a real-time game... a two second delay?
Peon
Yeah, fixing his tearing problem in a Pong game by suggesting "vertical retrace synch" is like trying to kill a flea with a steamhammer (from a old phrase I heard). It can't be that complex a problem. Let's make sure he (and I) got the basics down, like allocating the Bitmap memory and BitBlt correctly. If he can't solve tearing in a simple Pong game, there's no way my PacMan will look smooth either.

VazGames.com

Phil P

[edited by - PhilVaz on September 5, 2003 9:04:55 PM]
Thx, that makes a lot more sence, I will fix the things suggested and let you know how it goes.
THX,JAP
<< I will fix the things suggested and let you know how it goes. >>

Let me know what happens. Also I see why you are doing Sleep(2000) and Sleep(3000) to wait for your DisplaySplashScreen, DisplayWinScreen, DisplayLoseScreen. I think a better way might be to use an int countdown, and display your text once a frame, and decrement countdown until it reaches zero. That way Windows doesn't get hogged of messages during the Sleep.

You should call your DisplaySplash, Win, and Lose Screen functions inside your GameLoop and use another variable, like int game_state, to switch between states.

When game_state = 0 you set countdown (to 5000 or whatever) and DisplaySplashScreen (and here you decrement countdown by 1 and when it reaches 0 you set game_state to 3)

When game_state = 1 you set countdown and DisplayWinScreen

When game_state = 2 you set countdown and DisplayLoseScreen

When 3 you are playing the game, etc. During playing of game and you win, set game_state = 1, when lose set game_state = 2

At the beginning of your GameLoop you need to check game_state

That's how Lamothe does it anyway (called a "finite state machine"). I assume most "pros" use this game_state type thing. It makes your logic clearer. All those functions get called from your GameLoop, you just have to test for game_state first.

Phil P

[edited by - PhilVaz on September 5, 2003 11:04:28 PM]
Phil, I tried allocating the memeory for the DC's for the blitting of the bitmaps in the GameInit function and deleting them in the GameShutdown, so now I have draw functions for each the paddle and the ball with only the blitting because the DC's are now global, I thought as well that this would help a lot. The tearing acctually got a little worse when making the bitmap DC's global and allocated thoughtout the game, I'm stumpped again as to why I can't fix this tearing problem. For now I have stopped trying to finish the game and I'm trying to fix the graphics issues, if you have any other thoughts let me know.
Oh, one more thing, if you use double buffering for your game, how do you go about doing it, because I use an hdc in memory. I'm not sure if there is another way to do it.

[edited by - JIMbond21 on September 6, 2003 7:38:54 PM]
THX,JAP
<< Oh, one more thing, if you use double buffering for your game, how do you go about doing it, because I use an hdc in memory. I'm not sure if there is another way to do it. >>

I got my double buffer code from GameTutorials.com, not sure what else to tell you to fix the tearing. Hopefully I'll be finishing a pacman clone in a few days with bitmaps, with I hope no tearing. Here is my asteroids game that uses double buffer, very smooth but I use only GDI primitives (no bitmaps)

Vazteroids.cpp EXE

Also my simple cat animation bitmap demo, do you see tearing here? (I slowed it down with a countdown, it could be much faster) I don't see any tearing but it is quite small....

CatDemo.cpp EXE

Phil P

[edited by - PhilVaz on September 7, 2003 8:08:45 AM]

This topic is closed to new replies.

Advertisement