Skip to content

Instantly share code, notes, and snippets.

@fp64
Last active February 17, 2025 05:09
Show Gist options
  • Select an option

  • Save fp64/2e25143e754424d4a2f592d9493451bc to your computer and use it in GitHub Desktop.

Select an option

Save fp64/2e25143e754424d4a2f592d9493451bc to your computer and use it in GitHub Desktop.
Basic WinAPI & SDL2 graphical program.
// Public Domain under http://unlicense.org, see link for details.
// Self-reference: https://gist.github.com/fp64/2e25143e754424d4a2f592d9493451bc
// Expanded version: https://gist.github.com/fp64/1a3166ca35118167af8839b40d4e7b94
// Minimalistic dual-backend C program for plain WinAPI and SDL2,
// that creates a (non-resizable) window and provides keyboard input
// and in-memory colorbuffer to draw to (no GAPI, i.e. softrender).
// Compilation, WinAPI (assuming MinGW):
// gcc -std=c99 -O2 demo.c -o demo.exe -mwindows -static
// Compilation, SDL (assuming gcc and a shell supporting backticks):
// gcc -std=c99 -O2 `sdl2-config --cflags` demo.c -o demo `sdl2-config --libs`
// Actual platform-independent logic is placed in callback()
// function below, which is invoked every frame.
// On both backends, colorbuffer is contiguous R8G8B8A8 (not BGRA)
// memory region, and has (0,0) at top-left corner.
// On both backends, nonzero keys[...] means the key is currently
// down. Which indices correspond to which keys differs between
// backends, but ' ' (Space), '0'..'9', 'A'..'Z' (uppercase),
// 8 (Backspace), 9 (Tab), 13 (Return), 27 (Esc), 112..123 (F1..F12),
// 37..40 (left/up/right/down arrow keys) all agree in both.
// Non-negative return value of callback() signals quitting.
static int width=640,height=480;
static unsigned char *colorbuffer=0;
static unsigned char keys[256]={0};
extern int callback(void);
//== Platform-dependent section ================================================
#if !defined(USE_SDL) && !defined(_WIN32)
#define USE_SDL 1 // Can be overridden via -DUSE_SDL=[...] option.
#endif
#if USE_SDL // SDL2 backend.
#include "SDL.h" // See https://nullprogram.com/blog/2023/01/08/
int main(int argc,char **argv)
{
SDL_Window *window=NULL;
SDL_Surface *surface=NULL;
(void)argc; (void)argv;
if(SDL_Init(SDL_INIT_VIDEO)<0)
{
SDL_Log("SDL_Init failed: %s\n",SDL_GetError());
return 1;
}
if(!(window=SDL_CreateWindow("demo",
SDL_WINDOWPOS_UNDEFINED,SDL_WINDOWPOS_UNDEFINED,width,height,0)))
{
SDL_Log("SDL_CreateWindow failed: %s\n",SDL_GetError());
return 1;
}
surface=SDL_CreateRGBSurfaceWithFormat(0,width,height,32,SDL_PIXELFORMAT_RGBA32);
SDL_SetSurfaceBlendMode(surface,SDL_BLENDMODE_NONE);
colorbuffer=(unsigned char *)(surface->pixels);
while(1)
{
SDL_Event e;
int quit=0;
while(SDL_PollEvent(&e)!=0)
{
int key=e.key.keysym.sym;
if(key>='a'&&key<='z') key+='A'-'a'; // Convert A..Z to VK_*.
if(key>=0x4000003A&&key<=0x40000045) key+=0x70-0x4000003A; // Convert F1..F12 to VK_*.
if(key>=0x4000004F&&key<=0x40000052) key=0x25+((0x1302>>((key-0x4000004F)*4))&15); // Convert arrow keys to VK_*.
if(key&~255) key=255;
if(e.type==SDL_QUIT) quit=1;
if(e.type==SDL_KEYDOWN) keys[key]=1;
if(e.type==SDL_KEYUP) keys[key]=0;
}
quit|=callback();
if(quit) break;
SDL_BlitSurface(surface,NULL,SDL_GetWindowSurface(window),NULL);
SDL_UpdateWindowSurface(window);
}
SDL_FreeSurface(surface);
SDL_DestroyWindow(window);
SDL_Quit();
return 0;
}
#else // WinAPI backend.
#include <windows.h>
int WINAPI WinMain(HINSTANCE hInstance,HINSTANCE hPrevInstance,LPSTR lpCmdLine,int nCmdShow)
{
HWND hwnd=NULL;
WNDCLASSA wc={0}; // No need for extended window class/style.
RECT rect={0,0,width,height};
DWORD style=WS_POPUP|WS_CAPTION|WS_SYSMENU|WS_MINIMIZEBOX;
(void)hPrevInstance; (void)lpCmdLine; (void)nCmdShow;
wc.lpfnWndProc=DefWindowProcA; // No need for custom WndProc.
wc.hInstance =hInstance;
wc.hCursor =LoadCursor(NULL,IDC_ARROW); // hIcon is fine being NULL, but not hCursor.
wc.lpszClassName="Demo";
if(!RegisterClassA(&wc)) return 1;
AdjustWindowRect(&rect,style,0); // width and height correspond to "client area", not the whole window.
if(!(hwnd=CreateWindowA(wc.lpszClassName,"demo",
style|WS_VISIBLE,
CW_USEDEFAULT,CW_USEDEFAULT,rect.right-rect.left,rect.bottom-rect.top,
NULL,NULL,hInstance,NULL))) return 1;
if(!(colorbuffer=(unsigned char*)LocalAlloc(LPTR,(SIZE_T)(width*height*4*2)))) return 1;
while(1)
{
BITMAPINFOHEADER bmih={sizeof(BITMAPINFOHEADER),width,-height,1,32,BI_RGB,0,0,0,0,0}; // Top-down.
HDC hdc=NULL;
MSG msg;
int x,y,i,quit=0;
while(PeekMessageA(&msg,NULL,0,0,PM_REMOVE))
{
quit|=(msg.message==WM_QUIT);
DispatchMessageA(&msg); // No need for TranslateMessageA(&msg);
}
for(i=0;i<256;++i) keys[i]=(unsigned char)(GetKeyState(i)>>15);
quit|=callback();
for(y=0;y<height;++y) for(x=0;x<width;++x) for(i=0;i<4;++i) // Convert to BGRA.
colorbuffer[(width*height+y*width+x)*4+i]=colorbuffer[(y*width+x)*4+((2-i)&3)];
if(quit||!(hdc=GetDC(hwnd))) break;
StretchDIBits(
hdc,
0,0,width,height,
0,0,width,height,
colorbuffer+(width*height*4),
(BITMAPINFO*)(&bmih),
DIB_RGB_COLORS,
SRCCOPY);
InvalidateRect(hwnd,NULL,FALSE); // Actually initiate redrawing.
ReleaseDC(hwnd,hdc);
}
LocalFree(colorbuffer);
DestroyWindow(hwnd);
return 0;
}
#endif
//== Platform-independent section ==============================================
int callback(void)
{
int x,y,i;
for(y=0;y<height;++y) for(x=0;x<width;++x) for(i=0;i<4;++i)
colorbuffer[(y*width+x)*4+i]=(unsigned char)((0xFFAA7722u+(unsigned)((x^y)&16)*512u)>>(8*i));
return keys['Q']||keys[27];
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment