Double fault buffering (updated December 17, 2013)

INTRODUCTION AND RELATED INFORMATION:

I have a difficult picture to implement in my main window WM_PAINT handler.

I presented the image below to illustrate this:

enter image description here

The main window has static controls, not buttons with the SS_NOTIFY style.

When the user clicks on them, certain actions take place in the program that are currently irrelevant.

The following figure shows which static controls are in the main window:

enter image description here

The map on the orange panel is an EMF file, the upper left and right logo is PNG , and other bitmap s images.

Visual Styles activated using the #pragma . I also use GDI+ with GDI .

The project was created as an empty project, and I encoded everything from scratch.

To implement this task, I decided to draw the whole image in WM_PAINT and place a transparent static control on top of the images in the picture that correspond to them.

To keep my code clean and simple, I created functions that implement the above, so my WM_PAINT handler can be as small as possible.

UPDATE # 1 (updated December 17, 2013):

To implement the advice received from the arx member, I am sending one source code that can be compiled and which can reproduce the problem:

 #include <windows.h> #include <windowsx.h> #include <commctrl.h> #include <gdiplus.h> #pragma comment( linker, "/manifestdependency:\"type='win32' \ name='Microsoft.Windows.Common-Controls' version='6.0.0.0' \ processorArchitecture='*' publicKeyToken='6595b64144ccf1df' \ language='*'\"" ) #pragma comment( lib, "comctl32.lib") #pragma comment( lib, "Msimg32.lib" ) #pragma comment( lib, "Gdiplus.lib" ) using namespace Gdiplus; // variable for storing the instance static HINSTANCE hInst; // variables for painting the window static HBRUSH hbPozadina, // gray background brush for grid on the top BlueFrame, // needed to frame blue static controls hbr; // orange brush for orange panel /********* helper functions for WM_PAINT **********/ // Fills triangle with gradient brush void GradientTriangle( HDC MemDC, LONG x1, LONG y1, LONG x2, LONG y2, LONG x3, LONG y3, COLORREF top, COLORREF bottom ) { TRIVERTEX vertex[3]; vertex[0].x = x1; vertex[0].y = y1; vertex[0].Red = GetRValue(bottom) << 8; vertex[0].Green = GetGValue(bottom) << 8; vertex[0].Blue = GetBValue(bottom) << 8; vertex[0].Alpha = 0x0000; vertex[1].x = x2; vertex[1].y = y2; vertex[1].Red = GetRValue(top) << 8; vertex[1].Green = GetGValue(top) << 8; vertex[1].Blue = GetBValue(top) << 8; vertex[1].Alpha = 0x0000; vertex[2].x = x3; vertex[2].y = y3; vertex[2].Red = GetRValue(bottom) << 8; vertex[2].Green = GetGValue(bottom) << 8; vertex[2].Blue = GetBValue(bottom) << 8; vertex[2].Alpha = 0x0000; // Create a GRADIENT_TRIANGLE structure that // references the TRIVERTEX vertices. GRADIENT_TRIANGLE gTriangle; gTriangle.Vertex1 = 0; gTriangle.Vertex2 = 1; gTriangle.Vertex3 = 2; // Draw a shaded triangle. GradientFill( MemDC, vertex, 3, &gTriangle, 1, GRADIENT_FILL_TRIANGLE); } // draws the background for the part of the window between header and footer void drawBackground( HDC MemDC, RECT r ) { /******* main window gradient background ********/ GradientTriangle( MemDC, r.right, r.bottom - r.top - 30, r.left, r.bottom - r.top - 30, r.left, r.top + 120, RGB( 0x95, 0xB3, 0xD7 ), RGB( 0xDB, 0xE5, 0xF1 ) ); GradientTriangle( MemDC, r.right, r.bottom - r.top - 30, r.right, r.top + 120, r.left, r.top + 120, RGB( 0x95, 0xB3, 0xD7 ), RGB( 0xDB, 0xE5, 0xF1 ) ); } // draws the header of the main window void drawHeader( HDC MemDC, RECT rect, HBRUSH hbPozadina ) { FillRect( MemDC, &rect, hbPozadina ); } // fills rectangle with gradient brush void GradientRectangle( HDC MemDC, LONG x1, LONG y1, LONG x2, LONG y2, COLORREF top, COLORREF bottom ) { // vertexes for static gradient color TRIVERTEX vertexS[2]; vertexS[0].x = x1; vertexS[0].y = y1; vertexS[0].Red = GetRValue(top) << 8; vertexS[0].Green = GetGValue(top) << 8; vertexS[0].Blue = GetBValue(top) << 8; vertexS[0].Alpha = 0x0000; vertexS[1].x = x2; vertexS[1].y = y2; vertexS[1].Red = GetRValue(bottom) << 8; vertexS[1].Green = GetGValue(bottom) << 8; vertexS[1].Blue = GetBValue(bottom) << 8; vertexS[1].Alpha = 0x0000; // Create a GRADIENT_RECT structure that // references the TRIVERTEX vertices. GRADIENT_RECT gRect; gRect.UpperLeft = 0; gRect.LowerRight = 1; // Draw a shaded rectangle. GradientFill( MemDC, vertexS, 2, &gRect, 1, GRADIENT_FILL_RECT_V ); } // fills the "button" with blue gradient and frames it with blue brush void FillButton( HDC MemDC, RECT rect, HBRUSH BlueFrame ) { // fill upper half of the rectangle GradientRectangle( MemDC, rect.left, rect.top, rect.right, rect.top + ( rect.bottom - rect.top ) / 2, RGB( 0x95, 0xB3, 0xD7 ), RGB( 0x4F, 0x8B, 0xBD ) ); // fill bottom half of the rectangle GradientRectangle( MemDC, rect.left, rect.top + ( rect.bottom - rect.top ) / 2, rect.right, rect.bottom, RGB( 0x4F, 0x8B, 0xBD ), RGB( 0x95, 0xB3, 0xD7 ) ); FrameRect( MemDC, &rect, BlueFrame ); } // draws the "status bar" at the bottom of the main window void drawFooter( HDC MemDC, RECT r, COLORREF top, COLORREF bottom ) { // down triangle GradientTriangle( MemDC, r.right, r.bottom, ( r.right - r.left ) / 2, r.bottom - r.top - 15, r.left, r.bottom, top, bottom ); // upper triangle GradientTriangle( MemDC, r.right, r.bottom - r.top - 30, ( r.right - r.left ) / 2, r.bottom - r.top - 15, r.left, r.bottom - r.top - 30, top, bottom ); // left triangle GradientTriangle( MemDC, r.left, r.bottom, ( r.right - r.left ) / 2, r.bottom - r.top - 15, r.left, r.bottom - r.top - 30, top, bottom ); // right triangle GradientTriangle( MemDC, r.right, r.bottom - r.top - 30, ( r.right - r.left ) / 2, r.bottom - r.top - 15, r.right, r.bottom, top, bottom ); } // draw orange panel on which map and 3 static controls will be drawn void drawOrangePanel( HDC MemDC, RECT r, COLORREF top, COLORREF bottom ) { // down triangle GradientTriangle( MemDC, r.right, r.bottom, r.left + ( r.right - r.left ) / 2, r.top + ( r.bottom - r.top ) / 2, r.left, r.bottom, top, bottom ); // upper triangle GradientTriangle( MemDC, r.right, r.top, r.left + ( r.right - r.left ) / 2, r.top + ( r.bottom - r.top ) / 2, r.left, r.top, top, bottom ); // left triangle GradientTriangle( MemDC, r.left, r.bottom, r.left + ( r.right - r.left ) / 2, r.top + ( r.bottom - r.top ) / 2, r.left, r.top, top, bottom ); // right triangle GradientTriangle( MemDC, r.right, r.top, r.left + ( r.right - r.left ) / 2, r.top + ( r.bottom - r.top ) / 2, r.right, r.bottom, top, bottom ); } void onPaint( HWND hwnd, WPARAM wParam, LPARAM lParam ) { PAINTSTRUCT ps; HDC hdc = BeginPaint( hwnd, &ps); RECT r; // rectangle for main window client area GetClientRect( hwnd, &r); HDC MemDC = CreateCompatibleDC(hdc); // back buffer // compatible bitmap for MemDC HBITMAP bmp = CreateCompatibleBitmap( hdc, r.right - r.left, r.bottom - r.top ), oldBmp = (HBITMAP)SelectObject( MemDC, bmp ); // needed for cleanup // draw background for middle part of the window drawBackground( MemDC, r ); // draw header with grid lines RECT rect; // position it properly at the top rect.left = r.left; rect.top = r.top; rect.right = r.right; rect.bottom = 120; drawHeader( MemDC, rect, hbPozadina ); // draw "status bar" drawFooter( MemDC, r, RGB( 0x48, 0xAC, 0xC6), RGB( 0x31, 0x83, 0x99 ) ); /******* draw static control background ****/ //======== top left static control ======// //position it properly rect.left = ( 3 * ( r.right - r.left ) / 4 - 340 ) / 3; rect.top = 120 + ( r.bottom - r.top - 450 ) / 3; rect.right = 150 + ( 3 * ( r.right - r.left ) / 4 - 340 ) / 3; rect.bottom = 270 + ( r.bottom - r.top - 450 ) / 3; // draw gradient button FillButton( MemDC, rect, BlueFrame ); //======================== draw orange panel =================// //position it properly rect.left = 3 * ( r.right - r.left ) / 4 - 40; rect.top = r.top + 140; rect.right = rect.left + ( r.right - r.left ) / 4; rect.bottom = rect.top + ( r.bottom - r.top - 190 ); drawOrangePanel( MemDC, rect, RGB( 0xFF, 0xC8, 0xAA ), RGB( 0xFF, 0x96, 0x48 ) ); /****** draw back buffer on the screen DC *******/ BitBlt( hdc, 0, 0, r.right - r.left, r.bottom - r.top, MemDC, 0, 0, SRCCOPY ); /************** cleanup *******************/ SelectObject( MemDC, oldBmp ); DeleteObject(bmp); // compatible bitmap for MemDC DeleteDC(MemDC); EndPaint( hwnd, &ps); } // WinMain procedure LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) { switch(msg) { case WM_CREATE: { //******** brushes ***********// // load gray background brush for the top banner hbPozadina = CreateSolidBrush( RGB( 230, 230, 230 ) ); // brush for orange panel that holds 3 static controls and a map hbr = CreateSolidBrush( RGB( 255, 163, 94 ) ); // blue frame for blue static controls BlueFrame = CreateSolidBrush( RGB(79, 129, 189) ); /*******************************************/ } return (LRESULT)0; case WM_ERASEBKGND: return (LRESULT)1; // so we avoid flicker ( all painting is in WM_PAINT ) case WM_PAINT: { // paint the picture onPaint( hwnd, wParam, lParam ); } return (LRESULT)0; case WM_SIZE: InvalidateRect( hwnd, NULL, FALSE ); return (LRESULT)0; case WM_CLOSE: // destroy brushes DeleteObject(hbPozadina); DeleteObject(hbr); DeleteObject(BlueFrame); DestroyWindow(hwnd); return (LRESULT)0; case WM_DESTROY: PostQuitMessage(0); return (LRESULT)0; default: return DefWindowProc(hwnd, msg, wParam, lParam); } return 0; } // WinMain int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) { // store hInstance in global variable for later use hInst = hInstance; WNDCLASSEX wc; HWND hwnd; MSG Msg; /**** variables for GDI+ initialization ******/ GdiplusStartupInput gdiplusStartupInput; ULONG_PTR gdiplusToken; /********* Initialize GDI+. ********/ GdiplusStartup(&gdiplusToken, &gdiplusStartupInput, NULL); /**** finished GDI+ initialisation *****/ // initialize common controls INITCOMMONCONTROLSEX iccex; iccex.dwSize = sizeof(INITCOMMONCONTROLSEX); iccex.dwICC = ICC_STANDARD_CLASSES ; InitCommonControlsEx(&iccex); // register main window class wc.cbSize = sizeof(WNDCLASSEX); wc.style = 0; wc.lpfnWndProc = WndProc; wc.cbClsExtra = 0; wc.cbWndExtra = 0; wc.hInstance = hInst; wc.hIcon = LoadIcon (NULL, IDI_APPLICATION); wc.hCursor = LoadCursor( NULL, IDC_ARROW ); wc.hbrBackground = NULL;//(HBRUSH)GetStockObject( WHITE_BRUSH ); wc.lpszMenuName = NULL; wc.lpszClassName = L"Main_Window"; wc.hIconSm = LoadIcon (NULL, IDI_APPLICATION); if(!RegisterClassEx(&wc)) { MessageBox( NULL, L"Window Registration Failed!", L"Error!", MB_ICONEXCLAMATION | MB_OK ); return 0; } // create main window hwnd = CreateWindowEx( 0, // WS_EX_COMPOSITED "improved" drawing of the edges L"Main_Window", L"", WS_OVERLAPPEDWINDOW, ( GetSystemMetrics(SM_CXMAXIMIZED) - 1020 ) / 2, ( GetSystemMetrics(SM_CYMAXIMIZED) - 600 ) / 2, 1020, 600, NULL, NULL, hInstance, 0 ); if(hwnd == NULL) { MessageBox( NULL, L"Window creation failed!", L"Error!", MB_ICONEXCLAMATION | MB_OK ); return 0; } ShowWindow(hwnd, nCmdShow); UpdateWindow(hwnd); while(GetMessage(&Msg, NULL, 0, 0) > 0) { TranslateMessage(&Msg); DispatchMessage(&Msg); } // shutdownd GDI+ GdiplusShutdown(gdiplusToken); return Msg.wParam; } 

I am working on Windows XP using the MS Visual Studio C++ 2008 Express Edition and the pure Win32 API .

One note: since Express VS does not have one , it has a resource editor, the resource file and resource header were created using ResEdit from here: http://www.resedit.net/ .

Problem:

To avoid flickering, I used double buffering, which I learned from * Paul Watt's articles on CodeProject, Charles Petzolds Programming Windows 5th edition, and Forgers WIN32.

Theoretically, everything is fine, and my code compiles without errors.

I pasted the code here: http://pastebin.com/zSYT1i8L

My English is not good enough to accurately describe the problems I am facing (all I can say is that the edges of the main window and the static controls redraw โ€œslowlyโ€ and they flicker), so I created a demo application that demonstrates them: http://www.filedropper.com/geotermistgrafika

MY EFFORTS TO SOLVE THE PROBLEM:

I processed WM_ERASEBKGND (returned (LRESULT)1 ), and I excluded the CS_VREDRAW and CS_HREDRAW from my window class, so this should not cause flickering.

There is no WS_CLIPCHILDREN style in my window, because part of the desktop image is visible where static controls are installed.

In my WM_SIZE handler, I have:

  • Moved static controls using the SetWindowPos(...) API and reduced flicker by adding the SWP_NOCOPYBITS flag.

  • Invalid full window with InvalidateRect( hWnd, NULL, FALSE ) , so this API does not send WM_ERASEBKGND if invalid (third parameter is FALSE ), but even if I try to use TRUE , the effect is the same

I implemented double buffering for the WM_PAINT handler, as in the examples found in the books / articles / tutorials above (doing everything in DC memory and do BitBlt(...) on the DC screen to avoid flickering).

I did not process the WM_SIZING message, not WM_WINDOWPOSCHANGING or WM_MOVING .

I used the GDIView tool ( http://www.nirsoft.net/utils/gdi_handles.html ) to track the GDI leaks .

Each time I resize / enlarge my window, GDIView shows +4 in the column for regions, which should mean that I am leaking regions, but I cannot understand how this is possible, since I do not use APIs that manipulate regions , and double-checked everything.

In my opinion, everything should be fine, and maybe it doesnโ€™t matter, but I just thought to mention it, maybe it is important.

If I add the WS_EX_COMPOSITED style to the main window, performance will not improve.

I tried to find an online example that would help me solve my problem, but all the tutorials are simple and do not cover this type of complex image.

IMPORTANT NOTE:

After my WM_PAINT handler is empty and calls the onPaint function in WM_ERASEBKGND with the device context obtained using the GetDC(..) API, the flicker disappears, but during the resizing, the window redraw was "oblique" and there was no problem with the edges of the main window resolved.

However, this is much better than the source code.

Question:

How to get rid of the problems demonstrated in my demo application above?

I hereby thank everyone who invests their time and efforts to try to help me.

Sincerely.

+6
source share
1 answer

I compiled and ran the code on Windows 7. Using a standard theme (which uses DWM ), it looked great when resized.

I switched to the Windows Classic theme (which disables DWM), and when I changed the window, many of the edges of the buttons changed. I suspect this is the problem you are seeing.

Tearing occurs when painting is not synchronized with the physical refresh of the screen. This results in a partial screen showing the old image and part of the screen showing the new image. The effect is especially noticeable on vertical lines moving horizontally.

Are you still using Windows XP?

As far as I know, the only way to prevent breaks on XP is to use DirectX for your picture and explicitly synchronize with VSYNC. Although you could use your existing drawing code and just draw the final bitmap using DirectX. Or maybe some other synchronization mechanism. I dont know.

However, since the problem is fixed in later versions of Windows, I would not do anything.

+3
source

All Articles