Jump to content
  • Advertisement
Sign in to follow this  
burnthepc

Double buffering - Win32 C API

This topic is 3582 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Recommended Posts

I've spent this afternoon adding double buffering to a program I'm writing. Finally have it working... only thing is it seems to make no difference! Here's the key bits of the code: HDC hdc, HDC buffer and HBITMAP bitmap are globals, and a few others. Code:
// Main message pump with rendering

hdc = GetDC(hwnd);
buffer = CreateCompatibleDC(hdc); 
bitmap = CreateCompatibleBitmap(hdc,CLIENT_X_SZ,CLIENT_Y_SZ);


while(msg.message != WM_QUIT)
{
	
	if(PeekMessageW(&msg, NULL, 0, 0, PM_REMOVE))
	{
		TranslateMessage(&msg);
		DispatchMessage(&msg);
	}
	else  {
		SelectObject(buffer, bitmap);
		world.render(buffer);
		BitBlt(hdc, 0, 0, CLIENT_X_SZ,CLIENT_Y_SZ, buffer, 0, 0, SRCCOPY);
	}

// From WinProc

case WM_PAINT:
			hdc = BeginPaint(hwnd,&ps);
                                                EndPaint(hwnd, &ps);
			return 0;

case WM_SIZE:
			CLIENT_X_SZ = LOWORD(lParam);
			CLIENT_Y_SZ =HIWORD(lParam);
			hdc = GetDC(hwnd);
			if(bitmap)
				DeleteObject(bitmap);
			buffer = CreateCompatibleDC(hdc); 
	bitmap = CreateCompatibleBitmap(hdc,CLIENT_X_SZ,CLIENT_Y_SZ);
			SelectObject(buffer, bitmap);
			return 0;

It renders as well as it did before, and I'm only drawing on the buffer and then blitting it to the hdc, no where else. It still has some nasty flicker. Could it have something to do with the WM_TIMER messages? Code:
	case WM_TIMER:
			// TO DO move this to use a better timer call
			world.update(16);
 

Share this post


Link to post
Share on other sites
Advertisement
Do you invalidate the client area?
With InvalidateRect for example, to force a redraw. Whenever you get a WM_PAINT message, windows will by default have painted the window white before letting you redraw. Try creating your window class without a background brush, set the hbrBackground member of the window class struct to NULL before creating it with RegisterClass.

Share this post


Link to post
Share on other sites
I don't invalidate it myself, but that does sound a possible cause.

I try setting the HBRUSH to NULL and also to GODAWFULPINK - should really be able to see if it's painting the background then. Thanks for the help!

Share this post


Link to post
Share on other sites
Seemed promising but no, it still flickers. Here's my win main section:



WorldManager world;
HDC hdc;
HDC buffer;
HBITMAP bitmap;
const int AI_UPDATE_ID = 1;
const int AI_TICK = 32;

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR lpCmdLine, int nShowCmd)
{
HWND hwnd;
WNDCLASS winclass;
hinstGLOBAL = hInstance;
MSG msg;

winclass.style = CS_VREDRAW | CS_HREDRAW;
winclass.lpfnWndProc = WndProc;
winclass.cbClsExtra = 0;
winclass.cbWndExtra = 0;
winclass.hInstance = hInstance;
winclass.hCursor = LoadCursor(NULL, IDC_ARROW);
winclass.hIcon = NULL;
winclass.hbrBackground = NULL;
winclass.lpszMenuName = NULL;
winclass.lpszClassName = PROGNAME;

if(!RegisterClass(&winclass))
{
MessageBox(NULL, L"Unable to register class on startup", PROGNAME, MB_ICONERROR);
return 0;
}

hwnd = CreateWindow(PROGNAME, L"AI Framework", WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT, CW_USEDEFAULT, CLIENT_X_SZ, CLIENT_Y_SZ,
NULL, NULL, hInstance, NULL);

ShowWindow(hwnd, nShowCmd);
UpdateWindow(hwnd);


ZeroMemory(&msg, sizeof(msg));
hdc = GetDC(hwnd);
buffer = CreateCompatibleDC(hdc);
bitmap = CreateCompatibleBitmap(hdc,CLIENT_X_SZ,CLIENT_Y_SZ);


while(msg.message != WM_QUIT)
{

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

SelectObject(buffer, bitmap);
world.render(buffer);
BitBlt(hdc, 0, 0, CLIENT_X_SZ,CLIENT_Y_SZ, buffer, 0, 0, SRCCOPY);
}
}

}


LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
PAINTSTRUCT ps;
HDC hdc;

switch(message)
{

case WM_CREATE:
for(int i = 0; i < 100 ; ++i)
world.CreateMovingEntity(rand() % CLIENT_X_SZ, rand() % CLIENT_Y_SZ, 1000, 0.2, 10, VICSEK);

SetTimer(hwnd, AI_UPDATE_ID, AI_TICK, NULL);
return 0;
case WM_TIMER:
// TO DO move this to use a better timer call
world.update(16);
return 0;

case WM_PAINT:
hdc = BeginPaint(hwnd,&ps);
EndPaint(hwnd, &ps);
return 0;

case WM_KEYDOWN:
switch(wParam)
{
case VK_LEFT:
world.resize(100);
return 0;
case VK_RIGHT:
for(int i = 0; i < 100 ; ++i)
world.CreateMovingEntity(rand() % CLIENT_X_SZ, rand() % CLIENT_Y_SZ, 1000, 0.2, 10, VICSEK);
return 0;
return 0;
}

case WM_SIZE:
CLIENT_X_SZ = LOWORD(lParam);
CLIENT_Y_SZ =HIWORD(lParam);
hdc = GetDC(hwnd);
if(bitmap)
DeleteObject(bitmap);
buffer = CreateCompatibleDC(hdc);
bitmap = CreateCompatibleBitmap(hdc,CLIENT_X_SZ,CLIENT_Y_SZ);
SelectObject(buffer, bitmap);
return 0;

case WM_DESTROY:
DeleteDC(buffer);
DeleteObject(bitmap);
PostQuitMessage(0);
return 0;


}
return DefWindowProc(hwnd, message, wParam, lParam);
}




Share this post


Link to post
Share on other sites
Try removing the CS_HREDRAW and CS_VREDRAW from the class style, unless you need them. Also you can remove the WM_PAINT handler entirely, BeginPaint sends an erase background message I think, though it shouldn't matter if you don't have a background brush. Did anything change when you changed the background color, so that you saw the flicker in that color?
Also, do you always get flicker every frame, or only when you resize or move the window?
I see you have a world.resize call there.. does it change the window size?
It's also possible that something goes wrong when updating the bitmap. Try Calling GdiFlush() after it, sometimes you need to do that to allow GDI calls to finish.

Share this post


Link to post
Share on other sites
SO far nothing has changed it. It still flickers all the time when running. It's a kind of low level flicker that's at first hard to see but eventually it's headache inducing and hard to look at.

The world resize() call just reduces the entity count - doesn't touch anything with the API - just game logic. Setting the background to red didn't change anything (apart from when it resized it flashed red for a frame or so).

It seems to flicker more with greater number of entities on screen - so it could be a performance issue. (It could also be that 1% black on white is hard to see the flicker, and it only gets noticable once the entity count has been bumped up to 10% black on white.)

I've got a feeling it could be something with the timer call - it's hard coded to be called at about 60 fps. This only updates the logic and doesn't touch the rendering, but perhaps it's delaying it sometimes. Wouldn't that just mean the framerate dropped and not a flicker?

Here's some more code from the World manager call:

void WorldManager::render(HDC hdc)
{ // whitewash the back buffer
BitBlt(hdc, 0, 0, CLIENT_X_SZ,CLIENT_Y_SZ, 0, 0, 0, WHITENESS);

//print the number of entities on top left
wchar_t num_ent[80];
TextOut(hdc, 10,10, num_ent, wsprintf(num_ent, L"Entity count: %d Press left/right to add/remove entities", entities.size()) );

//render all entities
for(int i = 0; i < entities.size(); ++i)
{
entities->render(hdc);
}
}


And the entity render:


void MovingEntity::render(HDC& hdc)
{

Point tri_0(0,10), tri_1(-2,0), tri_2(2, 0);
Matrix transl;
transl.AddRotation(heading.dot(Point(0.0,10.0)));
transl.AddTranslate(loc);

transl.TransformPoint(tri_0);
transl.TransformPoint(tri_1);
transl.TransformPoint(tri_2);




MoveToEx(hdc, int(tri_0.x), int(tri_0.y), NULL);
LineTo(hdc, int(tri_1.x),int(tri_1.y));
LineTo(hdc, int(tri_2.x),int(tri_2.y));
LineTo(hdc, int(tri_0.x),int(tri_0.y));
}

Share this post


Link to post
Share on other sites
You still need to move the BitBlt to WM_PAINT. And if you're caching the HDC you get from BeginPaint() a lot of Very Bad Things will happen, particularly if you want your app to run on other OS's.

Share this post


Link to post
Share on other sites
Didn't have any effect. I tried:

while(msg.message != WM_QUIT)
{
...
SelectObject(buffer, bitmap);
world.render(buffer);
InvalidateRect ( hwnd, NULL, FALSE );
}
}


and

case WM_PAINT:

hdc = BeginPaint(hwnd,&ps);
BitBlt(hdc, 0, 0, CLIENT_X_SZ,CLIENT_Y_SZ, buffer, 0, 0, SRCCOPY);
EndPaint(hwnd, &ps);
return 0;

Share this post


Link to post
Share on other sites
Hmm, interesting.

What are you using for a timer? WM_TIMER isn't going to cut it for something as high frequency as 60Hz. Have you tried adding some Sleep() calls randomly around to see if you can cause a longer flicker?

Basically, the only thing I can see that would cause a flicker is if the backbuffer isn't correct when you blit, although I'm not sure why that would be.

You're not doing something strange like rendering in your timer call and clearing the backbuffer in the main loop are you? If you are there's a very good chance you'll end up blitting a backbuffer to the screen that hasn't been rendered to.

EDIT: Why this line: SelectObject(buffer, bitmap); ? You should only call SelectObject at creation time.

Share this post


Link to post
Share on other sites
Sign in to follow this  

  • Advertisement
×

Important Information

By using GameDev.net, you agree to our community Guidelines, Terms of Use, and Privacy Policy.

We are the game development community.

Whether you are an indie, hobbyist, AAA developer, or just trying to learn, GameDev.net is the place for you to learn, share, and connect with the games industry. Learn more About Us or sign up!

Sign me up!