News:

Download Pelles C here: http://www.pellesc.se

Main Menu

A GDI Double Buffered Example

Started by rweidner, Today at 05:04:36 AM

Previous topic - Next topic

rweidner

A GDI Double-Buffered Example (Pelles C)

Note: This is not meant to be a GDI32 programming tutorial. I am not an expert in GDI32 (yet), so I probably should not try to write a tutorial on it right now. :)

Instead, this is a one-file example intended to show a working GDI double-buffered program in Pelles C v13.00.9. It may also be useful to some people as a small foundation for GDI32 experiments.

Prereq (how I tested it)

I used the Windows SDK on my machine.

If you already have it installed, it is usually somewhere under:

C:\Program Files (x86)\Windows Kits

The one I am using specifically is here:

 C:\Program Files (x86)\Windows Kits\10\Lib\10.0.26100.0\um\x86  (libs)
 C:\Program Files (x86)\Windows Kits\10\Include\10.0.26100.0\um  (headers/includes)

If you do not have the Windows SDK, start here:

https://learn.microsoft.com/en-us/windows/apps/windows-sdk/

Side note: If you have Visual Studio / Build Tools installed, the Windows SDK may already be installed with it.


To compile in Pelles C

1. Create a new Win32 GUI Project (this example uses WinMain, not main)
2. Open Project -> Project Options
3. Click General -> Folders
4. Notice the drop-down menu that probably says Libraries
5. Add the path to the SDK library folder (under Windows Kits)
6. Change the drop-down menu to Includes
7. Add the path to the SDK include folder (under Windows Kits)
8. Create main.c
9. Paste the code below

Code (main.c)

#define WIN32_LEAN_AND_MEAN
#include <windows.h>

static const char g_szClassName[] = "WinGdiHelloBackbufferedClass";

static int   g_running = 1;
static HFONT g_font = NULL;

/* Backbuffer objects */
static HDC     g_backDC = NULL;
static HBITMAP g_backBmp = NULL;
static HBITMAP g_backOldBmp = NULL;
static int     g_backW = 0;
static int     g_backH = 0;

static void DestroyBackbuffer(void)
{
    if (g_backDC)
    {
        if (g_backOldBmp)
        {
            SelectObject(g_backDC, g_backOldBmp);
            g_backOldBmp = NULL;
        }

        if (g_backBmp)
        {
            DeleteObject(g_backBmp);
            g_backBmp = NULL;
        }

        DeleteDC(g_backDC);
        g_backDC = NULL;
    }

    g_backW = 0;
    g_backH = 0;
}

static int EnsureBackbuffer(HWND hwnd, int w, int h)
{
    HDC hdcWindow;
    HBITMAP newBmp;

    if (w <= 0 || h <= 0)
        return 0;

    if (g_backDC && g_backBmp && g_backW == w && g_backH == h)
        return 1;

    DestroyBackbuffer();

    hdcWindow = GetDC(hwnd);
    if (!hdcWindow)
        return 0;

    g_backDC = CreateCompatibleDC(hdcWindow);
    if (!g_backDC)
    {
        ReleaseDC(hwnd, hdcWindow);
        return 0;
    }

    newBmp = CreateCompatibleBitmap(hdcWindow, w, h);
    ReleaseDC(hwnd, hdcWindow);

    if (!newBmp)
    {
        DeleteDC(g_backDC);
        g_backDC = NULL;
        return 0;
    }

    g_backBmp = newBmp;
    g_backOldBmp = (HBITMAP)SelectObject(g_backDC, g_backBmp);
    g_backW = w;
    g_backH = h;

    return 1;
}

static void RenderSceneToBackbuffer(HWND hwnd)
{
    RECT rc;
    RECT textRc;
    HBRUSH whiteBrush;

    GetClientRect(hwnd, &rc);

    if (!EnsureBackbuffer(hwnd, rc.right - rc.left, rc.bottom - rc.top))
        return;

    whiteBrush = (HBRUSH)GetStockObject(WHITE_BRUSH);
    FillRect(g_backDC, &rc, whiteBrush);

    SetBkMode(g_backDC, TRANSPARENT);
    SetTextColor(g_backDC, RGB(0, 0, 0));

    if (g_font)
        SelectObject(g_backDC, g_font);

    SetRect(&textRc, 190, 200, rc.right, rc.bottom);
    DrawTextA(g_backDC, "Hello, GDI Backbuffer!", -1, &textRc, DT_LEFT | DT_TOP | DT_SINGLELINE);
}

LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
    switch (msg)
    {
    case WM_CREATE:
        g_font = CreateFontA(
            -40, 0, 0, 0, FW_NORMAL,
            FALSE, FALSE, FALSE,
            ANSI_CHARSET, OUT_DEFAULT_PRECIS,
            CLIP_DEFAULT_PRECIS, DEFAULT_QUALITY,
            DEFAULT_PITCH | FF_DONTCARE,
            "Arial"
        );
        return 0;

    case WM_SIZE:
    {
        int w = LOWORD(lParam);
        int h = HIWORD(lParam);
        EnsureBackbuffer(hwnd, w, h);
        return 0;
    }

    case WM_ERASEBKGND:
        /* We paint the full frame ourselves from a backbuffer. Prevent default erase flicker. */
        return 1;

    case WM_KEYDOWN:
        if (wParam == VK_ESCAPE)
        {
            DestroyWindow(hwnd);
            return 0;
        }
        break;

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

        RenderSceneToBackbuffer(hwnd);

        if (g_backDC && g_backBmp)
        {
            BitBlt(
                hdc,
                0, 0, g_backW, g_backH,
                g_backDC,
                0, 0,
                SRCCOPY
            );
        }

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

    case WM_DESTROY:
        DestroyBackbuffer();

        if (g_font)
        {
            DeleteObject(g_font);
            g_font = NULL;
        }

        g_running = 0;
        PostQuitMessage(0);
        return 0;
    }

    return DefWindowProcA(hwnd, msg, wParam, lParam);
}

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{
    WNDCLASSA wc;
    HWND hwnd;
    RECT wr;
    MSG msg;
    DWORD frameMs = 1000 / 60;
    DWORD lastTick = GetTickCount();

    (void)hPrevInstance;
    (void)lpCmdLine;

    ZeroMemory(&wc, sizeof(wc));
    wc.lpfnWndProc   = WndProc;
    wc.hInstance     = hInstance;
    wc.lpszClassName = g_szClassName;
    wc.hCursor       = LoadCursor(NULL, IDC_ARROW);
    wc.hbrBackground = NULL; /* no default brush; we fully repaint */

    if (!RegisterClassA(&wc))
        return 1;

    wr.left = 0;
    wr.top = 0;
    wr.right = 800;
    wr.bottom = 450;
    AdjustWindowRect(&wr, WS_OVERLAPPEDWINDOW, FALSE);

    hwnd = CreateWindowExA(
        0,
        g_szClassName,
        "GDI backbuffer hello",
        WS_OVERLAPPEDWINDOW,
        CW_USEDEFAULT, CW_USEDEFAULT,
        wr.right - wr.left,
        wr.bottom - wr.top,
        NULL, NULL, hInstance, NULL
    );

    if (!hwnd)
        return 1;

    ShowWindow(hwnd, nCmdShow);
    UpdateWindow(hwnd);

    while (g_running)
    {
        while (PeekMessageA(&msg, NULL, 0, 0, PM_REMOVE))
        {
            if (msg.message == WM_QUIT)
            {
                g_running = 0;
                break;
            }
            TranslateMessage(&msg);
            DispatchMessageA(&msg);
        }

        if (!g_running)
            break;

        if ((GetTickCount() - lastTick) >= frameMs)
        {
            lastTick = GetTickCount();

            /* Trigger one frame render */
            InvalidateRect(hwnd, NULL, FALSE); /* FALSE: do not erase background */
            UpdateWindow(hwnd);
        }
        else
        {
            Sleep(1);
        }
    }

    return 0;
}

If I missed a Pelles C option/setting that matters, let me know and I will update this post.