c++winapimemory-managementdirect2d

Why is my bitmap render target using so much memory


i was using a dc render target normally but i saw that I am getting a little bit of flickering, so i added a bitmap render target as to run the drawing operations on then draw on the dc acting as a double buffer my high memory usage (almost 60MB) is due to

pDCRenderTarget->CreateCompatibleRenderTarget(&pBitmapRenderTarget);

the huge memory consumption is on when I am resizing the window. (note first time posting an issue since i almost always find the question already asked)

My main.cpp:

#include <windows.h>
#include <d2d1.h>
#pragma comment(lib, "d2d1")

// Global variables
ID2D1Factory* pFactory = nullptr;
ID2D1DCRenderTarget* pDCRenderTarget = nullptr;
ID2D1BitmapRenderTarget* pBitmapRenderTarget = nullptr;

ID2D1Factory* GetFactory() {
    if (pFactory == nullptr) {
        // Create the Direct2D factory
        D2D1CreateFactory(D2D1_FACTORY_TYPE_SINGLE_THREADED, &pFactory);
    }
    return pFactory;
}


// Window Procedure
LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
    switch (message)
    {
    case WM_PAINT:
    {
    

        // Bind the DC render target to the HDC
        RECT rc;
        GetClientRect(hwnd, &rc);
        HDC hdc = GetDC(hwnd);
        pDCRenderTarget->BindDC(hdc, &rc);

        // Create a compatible bitmap render target
        pDCRenderTarget->CreateCompatibleRenderTarget(&pBitmapRenderTarget);

        // Begin drawing on the bitmap render target
        ID2D1Bitmap* pBitmap = nullptr;
        pBitmapRenderTarget->BeginDraw();

        // Clear the background
        pBitmapRenderTarget->Clear(D2D1::ColorF(D2D1::ColorF::White));

        // Draw a rectangle
        ID2D1SolidColorBrush* pBrush = nullptr;
        pBitmapRenderTarget->CreateSolidColorBrush(D2D1::ColorF(D2D1::ColorF::Blue), &pBrush);
        pBitmapRenderTarget->FillRectangle(D2D1::RectF(50, 50, 200, 200), pBrush);

        // End drawing
        pBitmapRenderTarget->EndDraw();

        // Get the bitmap from the render target
        pBitmapRenderTarget->GetBitmap(&pBitmap);

        // Draw the bitmap to the DC render target
        pDCRenderTarget->BeginDraw();
        pDCRenderTarget->DrawBitmap(pBitmap, D2D1::RectF(0, 0, rc.right, rc.bottom)); // Destination rectangle
        pDCRenderTarget->EndDraw();

        // Release resources
        pBrush->Release();
        pBitmap->Release();


        pBitmapRenderTarget->Release();
        pBitmapRenderTarget = nullptr;


        break;
    }
    case WM_DESTROY:
        PostQuitMessage(0);
        pDCRenderTarget->Release();
        pDCRenderTarget = nullptr;
        if (pFactory) pFactory->Release();
        break;

    case WM_CLOSE:

        DestroyWindow(hwnd);
        break;

    default:
        return DefWindowProc(hwnd, message, wParam, lParam);
    }
    return 0;
}

// Main Entry Point
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{
    // Register the window class
    const wchar_t CLASS_NAME[] = L"Direct2DDoubleBufferingWindow";

    WNDCLASS wc = {};
    wc.lpfnWndProc = WndProc;
    wc.hInstance = hInstance;
    wc.lpszClassName = CLASS_NAME;

    RegisterClass(&wc);

    // Create the window
    HWND hwnd = CreateWindowEx(
        0,                              // Optional window styles
        CLASS_NAME,                     // Window class
        L"Direct2D Double Buffering",   // Window title
        WS_OVERLAPPEDWINDOW,            // Window style
        CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,
        nullptr, nullptr, hInstance, nullptr);

    if (!hwnd)
        return -1;

    // Create a DC render target
    D2D1_RENDER_TARGET_PROPERTIES rtProps = D2D1::RenderTargetProperties(D2D1_RENDER_TARGET_TYPE_DEFAULT, D2D1::PixelFormat(DXGI_FORMAT_B8G8R8A8_UNORM, D2D1_ALPHA_MODE_IGNORE));
    GetFactory()->CreateDCRenderTarget(&rtProps, &pDCRenderTarget);
    ShowWindow(hwnd, nCmdShow);

    // Run the message loop
    MSG msg = {};
    while (GetMessage(&msg, nullptr, 0, 0))
    {
        TranslateMessage(&msg);
        DispatchMessage(&msg);
    }

    return 0;
}



Solution

  • The first thing to do is process WM_PAINT like instructed in the official documentation and in this Direct2D tutorial Drawing with Direct2D, ie: you need to tell Windows you have handled the WM_PAINT message.

    So you code should instead be like this:

    case WM_PAINT:
    {
        // ==> I will handle WM_PAINT
        PAINTSTRUCT ps;
        HDC hdc = BeginPaint(hwnd, &ps);
        
        // Bind the DC render target to the HDC
        RECT rc;
        GetClientRect(hwnd, &rc);
    
        pDCRenderTarget->BindDC(hdc, &rc);
    
        // Create a compatible bitmap render target
        pDCRenderTarget->CreateCompatibleRenderTarget(&pBitmapRenderTarget);
    
        ...
    
        pBitmapRenderTarget->Release();
        pBitmapRenderTarget = nullptr;
    
        // ==> now, I have handled WM_PAINT
        EndPaint(hwnd, &ps);
        break;
    

    If you don't do this, Windows will continue sending WM_PAINT messages continuously and this will eat your machine's resource (not really the WM_PAINT itself, but your code in there).

    Then, depending on your goals, you also maybe keep the pBitmapRenderTarget instead of creating it each time you handle WM_PAINT, it really depends what you plan to do when you "paint" in the general sense (real time updates, seldom changes, etc.)

    Note with Direct2D (and Direct3D, etc.) another way to do this is to just do BeginPaint+EndPaint once in WM_PAINT and do the rendering elsewhere, for example: https://gamedev.stackexchange.com/questions/12901/wm-paint-and-direct3d