Double buffering - Win32 C API

Started by
10 comments, last by Erik Rufelt 15 years, 2 months ago
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);
 
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.
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!
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);}


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.
Don't do the BitBlt() in your main loop, call InvalidateRect() instead, then BitBlt() in your WM_PAINT handler.
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));	}

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.
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;
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.

This topic is closed to new replies.

Advertisement