1. Problem

I have two buffers. Primary buffer, which is displayed on the screen and a secondary buffer where everything is drawn and then passed to the primary.

The Graphics object is created from the secondary buffer, which is associated with a bitmap of size 800x600. Naturally when you resize the window, the size of the bitmap has to change in order to prevent clipping issues. The secondary HDC gets updated, and the bitmap is copied to the primary.

The issue is that there is something left in the Graphics object associated with secondary HDC that generates clipping. The drawing region still stays 800x600 despite already being updated to something larger (1000x1000).

My question is what should I update inside the Graphics object (Without having to explicitly recreate it from the existing HDC) in order to make it's drawing region fit the bitmap size.

2. What I tried

The first thing I tried was recreating the Graphics object from the updated HDC. This method works and the drawing region fits the size of the bitmap. It does not meet the design standard however. Graphics should be reusable.

I also tried updating the clipping region of the graphics object using the SetClip method. Although that did not seem to be the problem.

This is how I create the buffers.

HDC CoreWindowFrame::InitPaint(HWND hWnd)
{
    windowHdc = GetDC(hWnd);
    HDC secondaryBuffer = CreateCompatibleDC(windowHdc);
    HBITMAP map = CreateCompatibleBitmap(windowHdc, width, height);
    SelectObject(secondaryBuffer, map);
    return secondaryBuffer;
}

This function is called on resize

void CoreWindowFrame::UpdateBitmap(int width, int height)
{
    HBITMAP map = CreateCompatibleBitmap(windowHdc, width, height);
    SelectObject(secondaryBuffer, map);

}

And this is message processing:

case WM_SIZE:
    ConsoleWrite("WM_SIZE RECIEVED");
    width = *((unsigned short*)&lParam);
    height = ((unsigned short*)&lParam)[1];
    UpdateBitmap(width, height);

break;
case WM_PAINT:
    ConsoleWrite("WM_PAINT RECIEVE");

    windowGraphics->Clear(Color(255, 255, 255));

    Pen* testPen = new Pen(Color(255, 0, 0), 1.0F);

    windowGraphics->DrawLine(testPen, 0, 0, calls*3, 100);
    BitBlt(windowHdc, 0, 0, updatedWidth, updatedHeight, secondaryBuffer, 0, 0, MERGECOPY);

3. Expected Result

The graphics object should be reusable and it should not be tossed away and replaced with a new one each time something is refreshed. Therefore it has to be updated accordingly in case anything is resized or changed. I expect the region to fit the size of the currently updated bitmap in the secondary HDC. The bitmap, as seen in the code, is updated properly. It is the Graphics object that does not. It acts like it still remembers some of the value from the previous BitMap which results in this behavior.

1 Answers

1
Barmak Shemirani On Best Solutions
windowHdc = GetDC(hWnd);
HDC secondaryBuffer = CreateCompatibleDC(windowHdc);
HBITMAP map = CreateCompatibleBitmap(windowHdc, width, height);
SelectObject(secondaryBuffer, map);
return secondaryBuffer;

HDC obtained from GetDC or BeginPaint cannot be reused, as noted in comments.

You can however reuse memory bitmap (from CreateCompatibleBitmap) and reuse memory dc (obtained from CreateCompatibleDC), although there is usually no point in reusing memory dc.

Moreover, cleanup is required to avoid resource leak. Call ReleaseDC when you are finished with GetDC. See documentation for relevant release/delete functions.

Do this for a simple paint routine:

case WM_PAINT:
{
    PAINTSTRUCT ps;
    HDC hdc = BeginPaint(hwnd, &ps);

    Gdiplus::Graphics gr(hdc);
    Gdiplus::Pen testPen(Gdiplus::Color(255, 0, 0), 1.0F);
    gr.Clear(Gdiplus::Color::White);
    gr.DrawLine(&testPen, 0, 0, 100, 100);

    EndPaint(hwnd, &ps);
    return 0;
}

Usually you don't need to do anything more. Just call InvalidateRect in response to WM_SIZE to update paint.

For double-buffering, or drawing on a canvas, you can create a memory bitmap and reuse it. If window size changes, then you have to call DeleteObject for the old bitmap, and create a new bitmap based on the new size. Or you can create a bitmap which matches the largest window size, then use this bitmap for all window sizes.

See the example below for double-buffer paint (however reusing hbitmap is not necessary in this case)

HBITMAP hbitmap = NULL;

void InitPaint(HWND hwnd)
{
    HDC hdc = GetDC(hwnd);
    //create a bitmap with max size:
    int w = GetSystemMetrics(SM_CXFULLSCREEN); 
    int h = GetSystemMetrics(SM_CYFULLSCREEN); 
    hbitmap = CreateCompatibleBitmap(hdc, w, h);
    ReleaseDC(hwnd, hdc);
}

void cleanup()
{
    if (hbitmap)
        DeleteObject(hbitmap);
}

...
case WM_PAINT:
{
    PAINTSTRUCT ps;
    HDC hdc = BeginPaint(hwnd, &ps);

    RECT rc; GetClientRect(hwnd, &rc);
    int w = rc.right;
    int h = rc.bottom;
    HDC memdc = CreateCompatibleDC(hdc);
    HBITMAP oldbmp = (HBITMAP)SelectObject(memdc, hbitmap);

    Gdiplus::Graphics gr(memdc);
    Gdiplus::Pen testPen(Gdiplus::Color(255, 0, 0), 1.0F);
    gr.Clear(Gdiplus::Color(255, 255, 255));
    gr.DrawLine(&testPen, 0, 0, w, h);

    BitBlt(hdc, 0, 0, w, h, memdc, 0, 0, SRCCOPY);

    //cleanup:
    SelectObject(memdc, oldbmp);
    DeleteDC(memdc);

    EndPaint(hwnd, &ps);
    return 0;
}


Summary:

GetDC doesn't create anything. It only returns a reference to an existing handle which is used by the system. When you are finished with this handle, release it. There is no way to optimize this any further.

Other GDI objects, like pen or bitmap, can be created for your program and they can be reused. It only takes a few nano-seconds to create/destroy a pen, so it's usually not worth the added complexity to store these objects and keeping track.

The main bottle-neck is when you draw on the screen, for example drawing a line. You have to talk to the graphics card and talk to the monitor which takes a while. You can use double-buffering to draw on a bitmap, then BitBlt on the screen. BitBlt can be slow too, depending on the system.

For animation, use double-buffering if you see flicker.

For better performance you can use newer technologies like Direct2D

If animation is still too slow, consider using a second thread to run any math type calculations (the second thread should not reference any window handle, such as HDC from GetDC or BeginPaint)