Sign in to follow this  

double buffering = colossal performance reduction??

This topic is 4547 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

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 this post


Link to post
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 this post


Link to post
Share on other sites
Quote:
Original post by silverphyre673
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.

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 this post


Link to post
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 this post


Link to post
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 this post


Link to post
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 this post


Link to post
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 this post


Link to post
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 protos
HBITMAP 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 function
int 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/event
LRESULT 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!

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

Share this post


Link to post
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 this post


Link to post
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 this post


Link to post
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 this post


Link to post
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 protos
LRESULT CALLBACK MainWndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam);

//windows main entry function
int 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/event
LRESULT 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 silverphyre673
I will send $5,000 US to whoever tells me the solution.

Hahaha thanks for the imaginary reward =)

Good luck!
\Jimmy H

[edit]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 this post


Link to post
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 silverphyre673
I will send $5,000 US to whoever tells me the solution.

Hahaha thanks for the imaginary reward =)

Good luck!
\Jimmy H

[edit]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.

Share this post


Link to post
Share on other sites

This topic is 4547 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.

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

Sign in to follow this