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.