# double buffering = colossal performance reduction??

## Recommended Posts

Hey all. I've been developing this worms clone (pure win32 API) in c++, and the screen has been flickering. I finally got around to implementing some double buffering. When I started calling SwapBuffers(), though, I got a colossal performance reduction. When I comment it out, it works normally. ANy guesses why? Here is what I have: - A global HDC to the HWND, "hdc" - A PIXELFORMATDESCRIPTOR, "pfd", for hdc, which says that it should be double buffered - When I call my Rendering function, I do all my drawing to hdc, and then call SwapBuffers on it as the last function call. This leads to a big performance reduction. I know that it does cause a performance hit, since you have to copy a large amount of data every render, but I see it all the time when I'm working with OpenGL, using (I thought) the same procedure, and I've never had this happen before. Any advice appreciated. Thanks.

##### Share on other sites
You don't copy the screen when swapping buffers; you just specify a different area in memory for the computer to paint to the screen.

##### Share on other sites
Quote:
 Original post by silverphyre673I know that it does cause a performance hit, since you have to copy a large amount of data every render, but I see it all the time when I'm working with OpenGL, using (I thought) the same procedure, and I've never had this happen before. Any advice appreciated.Thanks.

Like the poster before said, swapping buffers is probably the least intensive operation possible. What you really have are two memory buffers, both for storing screens. At any one time, one buffer is used to display on the screen, and the other is being drawn on. Then, instead of copying the entire buffer, the pointers to the two buffers are switched. That way when you start drawing after a swap you are actually drawing on the buffer the buffer that was just on screen.

##### Share on other sites
I know that actually, I just wrote a confused post :)

I still don't know why I'm taking this sudden performance hit, though. Whenever I see double buffering taught, like in my win32 API reference book, it shows two HDCs: one made through CreateDC(HWND), if I remember correctly, and another through CreateCompatibleDC(HDC). You draw everything to the second HDC, then somehow copy it to the first one for drawing all at once. Can anyone give me a coded example of how to do this? I tried, and failed. Thanks.

##### Share on other sites
If you're using OpenGL you want to enable double-buffering in OGL.
It looks like you're double-buffering using device contexts which is very slow.

##### Share on other sites
Your post is a bit confusing because OpenGL is not usually considered part of the win32 api. Nor do you typically draw to an HDC when writing OpenGL apps.

SwapBuffers doesn't just flip pointers around. It also has an implied flush and possible a v-sync as well. You might try calling glFlush before SwapBuffers and see if the perf hit moves to the flush. If so I don't know what to tell you. If not try disabling vsync and see if that helps.

##### Share on other sites
I'm not using opengl. I was just using it as an example; I didn't know that it did it differently. I'm just using pure win32 API - drawing ellipses, rectangles and lines.

##### Share on other sites
When working with a WINDOWED application, 'swapping buffers' involves copying the render-buffer to the screen. And using pure Win32 GDI, copy is the only method.

when working with a FULL SCREEN application, 'swapping buffers' is really a pointer-swap.

You get flickering if you render to the visible surface, that's just a fact of life. You get tearing if you swap or copy buffers without waiting for vertical synchronization. I suggest you switch to SDL.

##### Share on other sites
I'm not switching to SDL; the game is almost done. There must be some way to avoid the screen flickers. The game is definitely not that big, and the amount of flickering is unacceptable. It is over the whole screen - I'm not just talking about animation flickers when something like a picture of a bouncing ball moves around the screen. Even if you had nothing moving, you can still see some visible screen flickering. It's terrible.

##### Share on other sites
I agree with Wyrframe. It's true that often double buffering means swapping pointers, but not always. At least I've understood it in a way that the advantage of double buffering is drawing to a secondary buffer which is not visible, then either swapping this buffer (the pointer) or copying it. The advantage of the double buffer in both cases is that the whole visible screen/window surface is painted in a short time thus reducing flickering. Without the double buffer, when drawing shapes and copying pixels to the visible surface it might take longer than it takes to update the surface, so some of the shapes will be invisible during some of the updates which makes them flicker.

Here's how I use to do double buffers:
Notes:
You can keep the backbuffer DC if you want but remember Windows 98 only allows 5 DCs per thread
I just rewrote this from an old game I did, if you see any error tell me and I'll look into that (maybe I mistyped)

#include <windows.h>//definitions#define WINDOW_WIDTH 640#define WINDOW_HEIGHT 480//function protosHBITMAP CreateSurface(USHORT width, USHORT height, HWND compatibleHWnd);void ReleaseSurface(HBITMAP hBitmap);HRESULT PaintRectangle(SHORT x,					   SHORT y,					   USHORT width,					   USHORT height,					   COLORREF color,					   HBITMAP dstBitmap,					   HWND compatibleWindow);HRESULT Blt(SHORT dstX,			SHORT dstY,			USHORT srcX,			USHORT srcY,			USHORT width,			USHORT height,			HBITMAP srcBitmap,			HWND dstWindow);LRESULT CALLBACK MainWndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam);//windows main entry functionint APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nShowCmd){	HWND hWnd;	MSG msg;	//bitmap surface	HBITMAP backbuffer;	//window class setup	WNDCLASS wc;	wc.style = CS_HREDRAW | CS_VREDRAW;	wc.lpfnWndProc = MainWndProc;	wc.cbClsExtra = 0;	wc.cbWndExtra = 0;	wc.hInstance = hInstance;	wc.hIcon = LoadIcon(NULL, IDI_WINLOGO); //identifier for system icon	wc.hCursor = LoadCursor(NULL, IDC_ARROW); //identifier for system cursor	wc.hbrBackground = (HBRUSH) GetStockObject(BLACK_BRUSH);	wc.lpszMenuName = NULL;	wc.lpszClassName = "mainwindow";	if (RegisterClass(&wc) == 0)	{		MessageBox(NULL, "Couldn't register window class.", "doublebuffer", 0);		return 0;	}	hWnd = CreateWindow("mainwindow",		"doublebuffer",		WS_OVERLAPPEDWINDOW | WS_VISIBLE,		0,		0,		WINDOW_WIDTH,		WINDOW_HEIGHT,		0,		0,		hInstance,		NULL);	if (hWnd == NULL)	{		MessageBox(NULL, "Couldn't create window.", "doublebuffer", 0);		return 0;	}	//create offscreen surfaces	backbuffer = CreateSurface(WINDOW_WIDTH, WINDOW_HEIGHT, hWnd);	//window message loop	while (true)	{		//peek messages for all windows belonging to this thread		if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))		{			if (msg.message == WM_QUIT)	//the window has been destroyed				break;			TranslateMessage(&msg);			DispatchMessage(&msg); //send the message to the event handler		}		else		{			//idling - do the drawing here			//paint a rectangle onto the backbuffer			PaintRectangle(0, 0, WINDOW_WIDTH / 2, WINDOW_HEIGHT / 2, RGB(205, 0, 0), backbuffer, hWnd);			//blit the whole backbuffer onto the window			Blt(0, 0, 0, 0, WINDOW_WIDTH, WINDOW_HEIGHT, backbuffer, hWnd);		}	}	//release offscreen surfaces	ReleaseSurface(backbuffer);	return msg.wParam;}//window process/eventLRESULT CALLBACK MainWndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam){	switch (msg)	{	case WM_PAINT:		break;	case WM_CLOSE:		DestroyWindow(hWnd); //post a WM_DESTROY message and dsetroy the current window	case WM_DESTROY:		PostQuitMessage(0); //post a WM_QUIT message to the message queue and return		return 0;	}	//return a call to DefWndProc so all messages gets handled	return DefWindowProc(hWnd, msg, wParam, lParam);}HBITMAP CreateSurface(USHORT width, USHORT height, HWND compatibleHWnd){	HDC compatibleDC;	HBITMAP hBitmap;	//create a bitmap compatible with the specified window	compatibleDC = GetDC(compatibleHWnd); //retrieves the window device context	hBitmap = CreateCompatibleBitmap(compatibleDC, width, height); //creates a bitmap compatible with it	ReleaseDC(compatibleHWnd, compatibleDC); //releases the compatible window device context	return hBitmap; //got a bitmap surface - done!}void ReleaseSurface(HBITMAP hBitmap){	if (hBitmap)		DeleteObject(hBitmap);}HRESULT PaintRectangle(SHORT x,					   SHORT y,					   USHORT width,					   USHORT height,					   COLORREF color,					   HBITMAP dstBitmap,					   HWND compatibleWindow){	if (dstBitmap == NULL || compatibleWindow == NULL)		return -1;	//not good	HRESULT hResult;	HDC compatibleDC;	HDC dstDC;	RECT rect = {x, y, x + width, y + height};	HBRUSH hBrush;	/*	create a device context for the bitmap surface		which is compatible with the one of the compatible window	*/	compatibleDC = GetDC(compatibleWindow);	dstDC = CreateCompatibleDC(compatibleDC);	SelectObject(dstDC, dstBitmap);	hBrush = CreateSolidBrush(color);	hResult = FillRect(dstDC, &rect, hBrush);	DeleteObject(hBrush);	//delete/release the used device contexts	DeleteDC(dstDC);							//this DC was created	ReleaseDC(compatibleWindow, compatibleDC);	//this was just retrieved	return hResult;}HRESULT Blt(SHORT dstX,			SHORT dstY,			USHORT srcX,			USHORT srcY,			USHORT width,			USHORT height,			HBITMAP srcBitmap,			HWND dstWindow){	HDC srcDC;	HDC dstDC;	//create a device context for the destination window	dstDC = GetDC(dstWindow);	//create a device context for the source bitmap (compatible with the window)	srcDC = CreateCompatibleDC(dstDC);	SelectObject(srcDC, srcBitmap);	//blt!	BitBlt(dstDC, dstX, dstY, width, height, srcDC, srcX, srcY, SRCCOPY);	//delete/release the device contexts	DeleteDC(srcDC);	ReleaseDC(dstWindow, dstDC);	return 0;}

Good luck!

just fixed the code, it was too wide[/edit]

##### Share on other sites
It sounds to me like it's not double-buffering by means of page-flipping, but instead by copying the image across. It wont be page-fliping if you're not in fullscreen mode I'd guess.

##### Share on other sites
I figured maybe I should test how fast my example was so I don't post totally ineffective code without knowing it =). I tried to set the maximum frame limit to 120 FPS and drawing the 320 x 240 rectangle and copying the 640 x 480 backbuffer reached 84 FPS. Which I think is OK since it's plain Win32 GDI, windowed, after all.

Here's a frame limiter if you want to test it yourself:

UINT UpdateFrame(UINT requestedFPS){	static DWORD msNextSecond = 0;	//the frame of the next second	static DWORD msNextFrame = 0;	//the frame to delay until	static UINT frameOfSecond = 0;	//the current frame of this second	static UINT framesPerSecond = 0;	//increment the frame of this second	++frameOfSecond;	if (GetTickCount() >= msNextSecond)	{		//update the time of the next second in milliseconds		msNextSecond = GetTickCount() + 1000;		//update the amount of frames and reset the frame counter		framesPerSecond = frameOfSecond;		frameOfSecond = 0;	}	//delay until the time of the next frame in milliseconds	while (GetTickCount() < msNextFrame);	msNextFrame = GetTickCount() + 1000 / requestedFPS;	//update the time of the next frame in milliseconds	return framesPerSecond;}

Preferrably call it after the rectangle and blt calls (I set the maximum limit to 120 FPS)
//...
//idling - do the drawing here
//paint a rectangle onto the backbuffer
PaintRectangle(0, 0, WINDOW_WIDTH / 2, WINDOW_HEIGHT / 2, RGB(205, 0, 0), backbuffer, hWnd);
//blit the whole backbuffer onto the window
Blt(0, 0, 0, 0, WINDOW_WIDTH, WINDOW_HEIGHT, backbuffer, hWnd);
framesPerSecond = UpdateFrame(120);
//...

##### Share on other sites
If you're not using OpenGL you shouldn't be playing around with PIXELFORMATDESCRIPTOR. You should be creating your backbuffer via CreateCompatibleDC and CreateCompatibleBitmap.

If the backbuffer format doesn't exactly match the screen format GDI manually converts every pixel as it blt's. This would explain your perf problems.

You also shouldn't be calling SwapBuffers. Just draw to the memory DC (aka backbuffer) and BitBlt to the foreground when you're done. Assuming you have a compatible DC and bitmap the blt will probably be hardware-accellerated and very fast.

##### Share on other sites
OK, I tried this, and it doesn't work. Please, please correct it so that double-buffering will work, the contents of memDC are BitBlt'd to hdc, and everybody goes home happy. All it does is display the mouse coordinates (in my program anyway, but you could just specify dummy values for mouse_x and mouse_y). Currently, this displays nothing, although if you use hdc instead of memDC in the TextOut function, it works (flickeringly).

I will send $5,000 US to whoever tells me the solution.  //Stuff for drawing PAINTSTRUCT ps; RECT rc_client; //Screen DC HDC hdc = BeginPaint(hwnd, &ps); //Memory DC HDC memDC = CreateCompatibleDC(hdc); GetClientRect(hwnd, &rc_client); //String we will display std::string out; //Buffer for itoa char * val = new char[50]; //Get Values for mouse position, convert to strings, and add to "out" out = "Mouse is at { "; itoa(mouse_x, val, 10); out += val; out += ", "; itoa(mouse_y, val, 10); out += val; out += " }"; //Write out to the memory buffer TextOut(memDC, rc_client.right / 2 - 100, rc_client.bottom / 2 - 15, out.c_str(), out.length()); //Remember to free the memory! delete [] val; //This doesn't work. Nothing gets displayed on the screen. BitBlt(hdc, 0, 0, rc_client.right, rc_client.bottom, memDC, 0, 0, SRCCOPY); //End painting. I don't need to ReleaseDC(hdc), do I? EndPaint(hwnd, &ps); DeleteDC(memDC); #### Share this post ##### Link to post ##### Share on other sites I don't know if this has been missed out or if it's just not in the last code you posted but memDC doesn't refer to a backbuffer, it's just a DC compatible with the window. What you need is a bitmap in memory and a handle (HBITMAP) to it, then the memDC will be selected as the current HDC for this bitmap, then you can draw to the bitmap through memDC! HDCs only describes the object they're assigned to. Maybe you already know this, but BeginPaint/EndPaint should only be called in response to the WM_PAINT message. You can read more about that here: BeginPaint on MSDN (under the Remarks heading). I don't know if that is what you're doing but it's of course possible to call InvalidateRect to tell a redraw is needed and thus resulting in a WM_PAINT message, don't know how fast that is though. To make sure you get it right this time I put your code snippet in a generic WinMain function, in the message loop. I marked changed lines in your snippet with "//changed: ". I also exchanged your mouse_x and mouse_y with a POINT. Here's the code: #include <string>#include <windows.h>//function protosLRESULT CALLBACK MainWndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam);//windows main entry functionint APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nShowCmd){ UINT framesPerSecond; HWND hwnd; MSG msg; //backbuffer bitmap surface HDC compatibleDC; HBITMAP backbuffer; //window class setup WNDCLASS wc; wc.style = CS_HREDRAW | CS_VREDRAW; wc.lpfnWndProc = MainWndProc; wc.cbClsExtra = 0; wc.cbWndExtra = 0; wc.hInstance = hInstance; wc.hIcon = LoadIcon(NULL, IDI_WINLOGO); //identifier for system icon wc.hCursor = LoadCursor(NULL, IDC_ARROW); //identifier for system cursor wc.hbrBackground = (HBRUSH) GetStockObject(LTGRAY_BRUSH); wc.lpszMenuName = NULL; wc.lpszClassName = "mainwindow"; if (RegisterClass(&wc) == 0) { MessageBox(NULL, "Couldn't register window class.", "Double buffering", 0); return 0; } hwnd = CreateWindow("mainwindow", "Double buffering", WS_OVERLAPPEDWINDOW | WS_VISIBLE, 0, 0, 640, 480, 0, 0, hInstance, NULL); if (hwnd == NULL) { MessageBox(NULL, "Couldn't create window.", "Double buffering", 0); return 0; } //create a backbuffer bitmap compatibleDC = GetDC(hwnd); //retrieves the window device context backbuffer = CreateCompatibleBitmap(compatibleDC, 640, 480); //creates a bitmap compatible with it ReleaseDC(hwnd, compatibleDC); //releases the compatible window device context //window message loop while (true) { //peek messages for all windows belonging to this thread if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) { if (msg.message == WM_QUIT) //the window has been destroyed break; TranslateMessage(&msg); DispatchMessage(&msg); //send the message to the event handler } else { //idling - do the drawing here //retrieve the cursor position (on the screen) POINT mouse; GetCursorPos(&mouse); //Stuff for drawing RECT rc_client; //Screen DC //changed: HDC hdc = BeginPaint(hwnd, &ps); HDC hdc = GetDC(hwnd); //Memory DC HDC memDC = CreateCompatibleDC(hdc); SelectObject(memDC, backbuffer); GetClientRect(hwnd, &rc_client); //String we will display std::string out; //Buffer for itoa char * val = new char[50]; //Get Values for mouse position, convert to strings, and add to "out" out = "Mouse is at { "; itoa(mouse.x, val, 10); out += val; out += ", "; itoa(mouse.y, val, 10); out += val; out += " }"; //Write out to the memory buffer TextOut(memDC, rc_client.right / 2 - 100, rc_client.bottom / 2 - 15, out.c_str(), out.length()); //Remember to free the memory! delete [] val; //This doesn't work. Nothing gets displayed on the screen. BitBlt(hdc, 0, 0, rc_client.right, rc_client.bottom, memDC, 0, 0, SRCCOPY); //End painting. I don't need to ReleaseDC(hdc), do I? //not if you used BeginPaint since EndPaint does that automatically //but if you used GetDC it must be released //changed: EndPaint(hwnd, &ps); ReleaseDC(hwnd, hdc); DeleteDC(memDC); } } //release backbuffer bitmap DeleteObject(backbuffer); return msg.wParam;}//window process/eventLRESULT CALLBACK MainWndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam){ HDC hdc; PAINTSTRUCT ps; switch (msg) { case WM_CLOSE: DestroyWindow(hWnd); //post a WM_DESTROY message and dsetroy the current window case WM_DESTROY: PostQuitMessage(0); //post a WM_QUIT message to the message queue and return return 0; } //return a call to DefWndProc so all messages gets handled return DefWindowProc(hWnd, msg, wParam, lParam);} Quote:  Original post by silverphyre673I will send$5,000 US to whoever tells me the solution.

Hahaha thanks for the imaginary reward =)

Good luck!
\Jimmy H

Maybe I should also mention the reason why the area around the white colored area where the text is displayed is randomly colored, it's because nothing else was painted onto the backbuffer bitmap so the colors are remains of the memory before backbuffer was allocated.[/edit]

##### Share on other sites
Quote:
Original post by Jimmy H
I don't know if this has been missed out or if it's just not in the last code you posted but memDC doesn't refer to a backbuffer, it's just a DC compatible with the window. What you need is a bitmap in memory and a handle (HBITMAP) to it, then the memDC will be selected as the current HDC for this bitmap, then you can draw to the bitmap through memDC! HDCs only describes the object they're assigned to.

Maybe you already know this, but BeginPaint/EndPaint should only be called in response to the WM_PAINT message. You can read more about that here: BeginPaint on MSDN (under the Remarks heading). I don't know if that is what you're doing but it's of course possible to call InvalidateRect to tell a redraw is needed and thus resulting in a WM_PAINT message, don't know how fast that is though.

To make sure you get it right this time I put your code snippet in a generic WinMain function, in the message loop. I marked changed lines in your snippet with "//changed: ". I also exchanged your mouse_x and mouse_y with a POINT.

Here's the code:

*** Source Snippet Removed ***

Quote:
 Original post by silverphyre673I will send \$5,000 US to whoever tells me the solution.

Hahaha thanks for the imaginary reward =)

Good luck!
\Jimmy H

Maybe I should also mention the reason why the area around the white colored area where the text is displayed is randomly colored, it's because nothing else was painted onto the backbuffer bitmap so the colors are remains of the memory before backbuffer was allocated.[/edit]

I think... I think I'm falling in love with you :)

I have wired the ++rate to your unnumbered Micronesian bank account. Good luck.

I've understood most of the win32 API, but never been able to fix the screen-flickering as I'd like. Solved! w00t! I'll just encapsulate it so I never have to do it again (that is the project I'm working on now for most of the API).

Thanks again.

## Create an account

Register a new account

• ## Partner Spotlight

• ### Forum Statistics

• Total Topics
627662
• Total Posts
2978508

• 10
• 10
• 12
• 22
• 13