3
votes

I'm creating a game, in which a lot of drawing is done during the WM_PAINT message. There are several different places where the window is invalidated, forcing it to redraw. I have everything draw to an offscreen DC and then draw that to the window - to create non-flickering "frames".

However, every once in a while, everything suddenly starts being drawn incorrectly. Out of five bitmaps I use, the first three keep drawing more or less (but not completely) correctly. As in, all of the color information is correct for these three. The other two, which are drawn after these three, are drawn with the wrong colors - I think that white is still drawing as white, but everything else is being drawn as grey. And I'm not talking greyscale, I mean that everything except white is the same color - grey.

Also, when this starts happening, oftentimes everything is drawn too high - by about 20 to 30 pixels. Furthermore, fonts and message boxes stop working - all text is drawn in the default font (but, oddly, the correct color), and message boxes only appear briefly, with no text, and then disappear - but must be dismissed like regular (you have to press enter or else if you click on the main window, it does what it normally does when there's a message box open taking input - flashes and pings the error tone). So everything is royally screwed up.

I've been working on this project for a while now, and I've only begun seeing this error recently - although I haven't modified the piece of code below literally at all. It's really hard to test what could be doing this, because it only seems to happen once in a while.

Here's the code in my WM_CREATE and WM_PAINT in WndProc:

case WM_CREATE:
    {
        hdc = GetDC(hWnd);
        hdcmem = CreateCompatibleDC(hdc);
        RECT rc;
        GetClientRect(hWnd, &rc);
        hdcbm = CreateCompatibleBitmap(hdc, rc.right, rc.bottom);
        hbcmem = CreateCompatibleDC(hdcmem);
        hdcbmold = (HBITMAP)SelectObject(hdcmem, hdcbm);

        // Load bitmaps
        bg = LoadBitmap(hInst, MAKEINTRESOURCE(IDB_BACKGROUND));
        mainCont = LoadBitmap(hInst, MAKEINTRESOURCE(IDB_GAME_CONT));
        if(bg == NULL || mainCont == NULL)
            ThrowError("A bitmap failed to load.");
    }
    break;
case WM_PAINT:
{
    PAINTSTRUCT ps;
    BeginPaint(hWnd, &ps);

    // Background
    hdcold = (HBITMAP)SelectObject(hbcmem, bg);
    BitBlt(hdcmem, 0, 0, 237, 196, hbcmem, 0, 0, SRCCOPY);
    BitBlt(hdcmem, 237, 0, 237, 196, hbcmem, 0, 0, SRCCOPY);
    BitBlt(hdcmem, 237 * 2, 0, 237, 196, hbcmem, 0, 0, SRCCOPY);
    BitBlt(hdcmem, 0, 196, 237, 196, hbcmem, 0, 0, SRCCOPY);
    BitBlt(hdcmem, 237, 196, 237, 196, hbcmem, 0, 0, SRCCOPY);
    BitBlt(hdcmem, 237 * 2, 196, 237, 196, hbcmem, 0, 0, SRCCOPY);

    // Main Game Container
    hdcold = (HBITMAP)SelectObject(hbcmem, mainCont);
    BitBlt(hdcmem, 26, 26, 300, 300, hbcmem, 0, 0, SRCCOPY);

    // Side Info
    HBITMAP side = LoadBitmap(hInst, MAKEINTRESOURCE(IDB_SIDEINFO));
    hdcold = (HBITMAP)SelectObject(hbcmem, side);
    BitBlt(hdcmem, 339, 26, 154, 300, hbcmem, 0, 0, SRCCOPY);
    DrawLevelNumber(game.map.levelnumber);

    if (color)
        sprites = LoadBitmap(hInst, MAKEINTRESOURCE(IDB_COLOR_SPRITES));
    else sprites = LoadBitmap(hInst, MAKEINTRESOURCE(IDB_BLACKWHITE_SPRITES));
    hdcold = (HBITMAP)SelectObject(hbcmem, sprites);

    // Find x and y coordinate for the top left of the visible screen
    int x = game.player.x, y = game.player.y, ypos = 0;
    if (x < 4)  x = 4;
    if (x > 27) x = 27;
    if (y < 4)  y = 4;
    if (y > 27) y = 27;
    x -= 4;
    y -= 4;

    // Draw lower layer
    for (int i = 0; i < 9; i++)
    {
        for (int j = 0; j < 9; j++)
        {
            if (game.map.Layer_Two[x + i][y + j] != 0)
            {
                int xpos = game.get_pos(game.map.Layer_Two[x + i][y + j].get(), ypos, false);
                BitBlt(hdcmem, (i * 32) + 32, (j * 32) + 32, 32, 32, hbcmem, xpos, ypos, SRCCOPY);
            }
        }
    }

    // Draw upper layer
    for (int i = 0; i < 9; i++)
    {
        for (int j = 0; j < 9; j++)
        {
            if ((game.map.Layer_Two[x + i][y + j] != 0 && game.map.Layer_One[x + i][y + j] >= 64 && game.map.Layer_One[x + i][y + j] <= 111))
            {
                int xpos = game.get_pos(game.map.Layer_One[x + i][y + j].get(), ypos, true);
                BitBlt(hdcmem, (i * 32) + 32, (j * 32) + 32, 32, 32, hbcmem, xpos + 96, ypos, SRCPAINT);
                BitBlt(hdcmem, (i * 32) + 32, (j * 32) + 32, 32, 32, hbcmem, xpos, ypos, SRCAND);
            } else {
                int xpos = game.get_pos(game.map.Layer_One[x + i][y + j].get(), ypos, false);
                BitBlt(hdcmem, (i * 32) + 32, (j * 32) + 32, 32, 32, hbcmem, xpos, ypos, SRCCOPY);
            }
        }
    }

    // If it isn't started, show title
    if (!game.started)
    {

        HDC tmphdc = CreateCompatibleDC(hdcmem);
        HDC tmp = CreateCompatibleDC(tmphdc);
        RECT rc;
        GetClientRect(hWnd, &rc);
        string str = game.map.leveltitle.substr(0, game.map.leveltitle.length() - 1);
        TCHAR* tch = new TCHAR[str.length()];
        mbstowcs_s(NULL, tch, _tcslen(tch), str.c_str(), str.length());
        HFONT font = CreateFont(25, 0, 0, 0, FW_BOLD, false, false, false, ANSI_CHARSET, OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS, DEFAULT_QUALITY, FF_DONTCARE, NULL);
        SelectObject(tmp, font);
        DrawText(tmp, tch, str.length(), &rc, DT_CALCRECT);
        rc.right += 16;
        HBITMAP tmpbm = CreateCompatibleBitmap(hdcmem, rc.right, rc.bottom);
        HBITMAP tmpold = (HBITMAP)SelectObject(tmphdc, tmpbm);

        HBRUSH hbr = CreateSolidBrush(RGB(255, 255, 255));
        HPEN pen = CreatePen(PS_SOLID, 1, RGB(255, 255, 255));
        SelectObject(hdcmem, pen);
        SelectObject(hdcmem, hbr);
        Rectangle(hdcmem, 176 - (rc.right / 2), 243, 177 + (rc.right / 2), 248);
        hbr = CreateSolidBrush(RGB(128, 128, 128));
        pen = CreatePen(PS_SOLID, 1, RGB(128, 128, 128));
        SelectObject(hdcmem, pen);
        SelectObject(hdcmem, hbr);
        Rectangle(hdcmem, 176 - (rc.right / 2), 294, 177 + (rc.right / 2), 299);

        HBITMAP left = LoadBitmap(hInst, MAKEINTRESOURCE(IDB_LEFT));
        hdcold = (HBITMAP)SelectObject(hbcmem, left);
        BitBlt(hdcmem, 176 - (rc.right / 2) - 4, 243, 4, 56, hbcmem, 0, 0, SRCCOPY);
        HBITMAP right = LoadBitmap(hInst, MAKEINTRESOURCE(IDB_RIGHT));
        hdcold = (HBITMAP)SelectObject(hbcmem, right);
        BitBlt(hdcmem, 176 + (rc.right / 2) + ((rc.right % 2) != 0), 243, 4, 56, hbcmem, 0, 0, SRCCOPY);

        SelectObject(tmphdc, font);
        SetTextColor(tmphdc, RGB(255, 255, 0));
        SetBkColor(tmphdc, RGB(0, 0, 0));
        DrawText(tmphdc, tch, str.length(), &rc, DT_CENTER);
        BITMAP structBitmapHeader;
        memset( &structBitmapHeader, 0, sizeof(BITMAP) );
        HGDIOBJ hBitmap = GetCurrentObject(tmphdc, OBJ_BITMAP);
        GetObject(hBitmap, sizeof(BITMAP), &structBitmapHeader);
        BitBlt(hdcmem, 176 - (rc.right / 2), 247, structBitmapHeader.bmWidth, structBitmapHeader.bmHeight, tmphdc, 0, 0, SRCCOPY);

        DeleteDC(tmphdc);
        DeleteDC(tmp);
    }

    // If paused
    if (game.paused)
    {
        RECT rc;
        rc.top = 32;
        rc.left = 32;
        rc.bottom = 330;
        rc.right = 330;
        BitBlt(hdcmem, 32, 32, 288, 288, NULL, 0, 0, BLACKNESS);
        HFONT font = CreateFont(50, 0, 0, 0, FW_NORMAL, false, false, false, ANSI_CHARSET, OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS, DEFAULT_QUALITY, FF_DONTCARE, NULL);
        SelectObject(hdcmem, font);
        SetTextColor(hdcmem, RGB(255, 0, 0));
        SetBkColor(hdcmem, RGB(0, 0, 0));
        DrawText(hdcmem, L"PAUSED", 6, &rc, (DT_CENTER + DT_SINGLELINE + DT_VCENTER));
    }

    nums = LoadBitmap(hInst, MAKEINTRESOURCE(IDB_NUMBERS));
    hdcold = (HBITMAP)SelectObject(hbcmem, nums);
    for (int i = 100; i > 0; i /= 10) // coins and time left
    {
        int tmp;
        if (i == 100)
            tmp = game.coinsleft / 100;
        if (i == 10)
            tmp = ((game.coinsleft % 100) - (game.coinsleft % 10)) / 10;
        if (i == 1)
            tmp = game.coinsleft % 10;
        if (game.coinsleft < i && i > 1)
            tmp = 10;
        int ypos = game.get_num_pos(tmp, (game.coinsleft == 0));
        BitBlt(hdcmem, 417 + ((3 - (int)floor(log10((double)i)) * 17)), 215, 17, 23, hbcmem, 0, ypos, SRCCOPY);

        if (i == 100)
            tmp = game.timeleft / 100;
        if (i == 10)
            tmp = ((game.timeleft % 100) - (game.timeleft % 10)) / 10;
        if (i == 1)
            tmp = game.timeleft % 10;
        if (game.timeleft < i && i > 1)
            tmp = 10;
        if (game.map.timelimit == 0)
            tmp = 11;
        ypos = game.get_num_pos(tmp, (game.timeleft < 16 || game.map.timelimit == 0));
        BitBlt(hdcmem, 369 + ((3 - (int)floor(log10((double)i))) * 17), 125, 17, 23, hbcmem, 0, ypos, SRCCOPY);
    }

    if (game.onhint)
    {
        HBITMAP sidebg = LoadBitmap(hInst, MAKEINTRESOURCE(IDB_SIDEBG));
        hdcold = (HBITMAP)SelectObject(hbcmem, sidebg);
        BitBlt(hdcmem, 353, 165, 127, 146, hbcmem, 0, 0, SRCCOPY);
        HFONT font = CreateFont(18, 0, 0, 0, FW_BOLD, true, false, false, ANSI_CHARSET, OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS, DEFAULT_QUALITY, FF_DONTCARE, NULL);
        SelectObject(hdcmem, font);
        SetTextColor(hdcmem, RGB(0, 255, 255));
        SetBkColor(hdcmem, RGB(0, 0, 0));
        RECT rc;
        rc.top = 168;
        rc.left = 356;
        rc.bottom = 308;
        rc.right = 477;
        string str = "Hint: " + game.map.hint;
        TCHAR* tch = new TCHAR[str.length()];
        mbstowcs_s(NULL, tch, _tcslen(tch), str.c_str(), str.length());
        DrawText(hdcmem, tch, str.length(), &rc, DT_CENTER + DT_WORDBREAK);
    } else {
        hdcold = (HBITMAP)SelectObject(hbcmem, sprites);                // LOWER SIDE INFO
        if (game.player.key1 > 0)
            BitBlt(hdcmem, 352, 247, 32, 32, hbcmem, 192, 160, SRCCOPY);
        else BitBlt(hdcmem, 352, 247, 32, 32, hbcmem, 0, 0, SRCCOPY);
        if (game.player.key2 > 0)
            BitBlt(hdcmem, 384, 247, 32, 32, hbcmem, 192, 128, SRCCOPY);
        else BitBlt(hdcmem, 384, 247, 32, 32, hbcmem, 0, 0, SRCCOPY);
        if (game.player.key3 > 0)
            BitBlt(hdcmem, 416, 247, 32, 32, hbcmem, 192, 224, SRCCOPY);
        else BitBlt(hdcmem, 416, 247, 32, 32, hbcmem, 0, 0, SRCCOPY);
        if (game.player.key4)
            BitBlt(hdcmem, 448, 247, 32, 32, hbcmem, 192, 192, SRCCOPY);
        else BitBlt(hdcmem, 448, 247, 32, 32, hbcmem, 0, 0, SRCCOPY);
        if (game.player.mod1)
            BitBlt(hdcmem, 352, 279, 32, 32, hbcmem, 192, 256, SRCCOPY);
        else BitBlt(hdcmem, 352, 279, 32, 32, hbcmem, 0, 0, SRCCOPY);
        if (game.player.mod2)
            BitBlt(hdcmem, 384, 279, 32, 32, hbcmem, 192, 288, SRCCOPY);
        else BitBlt(hdcmem, 384, 279, 32, 32, hbcmem, 0, 0, SRCCOPY);
        if (game.player.mod3)
            BitBlt(hdcmem, 416, 279, 32, 32, hbcmem, 192, 320, SRCCOPY);
        else BitBlt(hdcmem, 416, 279, 32, 32, hbcmem, 0, 0, SRCCOPY);
        if (game.player.mod4)
            BitBlt(hdcmem, 448, 279, 32, 32, hbcmem, 192, 352, SRCCOPY);
        else BitBlt(hdcmem, 448, 279, 32, 32, hbcmem, 0, 0, SRCCOPY);
    }
    BitBlt(hdc, 0, 0, 518, 401, hdcmem, 0, 0, SRCCOPY);

    EndPaint(hWnd, &ps);
}
    break;
case WM_DESTROY:
    SelectObject(hdc,hdcold);
    DeleteDC(hdcmem);
    DeleteDC(hbcmem);
    ReleaseDC(hWnd, hdc);
    DeleteObject(bg);
    PostQuitMessage(0);
    break;

I'd also really appreciate any advice/criticism anyone could provide on syntax, efficiency and/or better ways of doing things in my code. I'm pretty new to Win32.

1

1 Answers

9
votes

You are leaking GDI objects.

Every time you create a brush or load a bitmap you are creating a GDI object. Windows limits the number of GDI objects you can create. If you repeatedly create objects without deleting them you will hit the limit and creation of further objects will fail. When this happens your display starts to look wrong - you see the wrong colour, default fonts, etc.

Task Manager will show you the number of GDI Objects that a process has allocated (in the Process tab go to View | Select Columns). The value may fluctuate a little but it shouldn't grow over time.

The standard pattern for using GDI is:

  1. Create your object (e.g. a brush with CreateSolidBrush).
  2. Select your object into the device context using SelectObject, and remember the original object (the return value).
  3. Do your drawing or whatever.
  4. Select the original object back into the device context using SelectObject. (Of course, this also unselects your object from the device context.)
  5. Delete your object using DeleteObject.

You are only doing half of step 2 and skipping steps 4 and 5.

Then note that steps 1 and 5 don't need to be repeated in every paint operation. You can move them to program initialization and termination respectively. And cleanup at program termination can be skipped because the OS is about to do that anyway.

And finally, loading bitmaps is a moderately slow operation, so you definitely don't want to be doing it repeatedly. This is the point of GDI providing handles to objects.