Last active
February 17, 2025 04:59
-
-
Save fp64/1a3166ca35118167af8839b40d4e7b94 to your computer and use it in GitHub Desktop.
Somewhat basic WinAPI & SDL2 graphical program.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // Public Domain under http://unlicense.org, see link for details. | |
| // Self-reference: https://gist.github.com/fp64/1a3166ca35118167af8839b40d4e7b94 | |
| // Minimalist version: https://gist.github.com/fp64/2e25143e754424d4a2f592d9493451bc | |
| // Somewhat minimalistic dual-backend C program for plain WinAPI and SDL2, | |
| // that creates a (possibly fullscreen) 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` | |
| // 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. | |
| static int width=0,height=0; // Setup during init(). May be updated during frame(). | |
| static unsigned char *colorbuffer=0; // Setup during init(). May be updated during frame(). | |
| static unsigned char keys[256]={0}; // Updated during frame(). | |
| static int mouse_x=0,mouse_y=0,mouse_b=0; // Updated during frame(). | |
| // Platform-independent entry point, mimicing main(). NOTE: argv is assumed UTF-8. | |
| extern int entrypoint(int argc,char **argv); | |
| // Initialization. Create window and fill width/height/colorbuffer. Returns zero on failure. | |
| static int init(int w,int h,int mode); // 0=borderless, 1=fixed, 2=resizable, 3=fullscreen. | |
| // Draw window and then process messages and update width/height/colorbuffer/keys. | |
| // Returns zero if needs to quit. | |
| static int frame(void); | |
| // Handy functions. | |
| static double get_time_in_seconds(void); | |
| static void set_window_title(const char *title); | |
| static void sleep(double time_in_seconds); | |
| //== 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. Can be forced via -DUSE_SDL=1 option. | |
| #include "SDL.h" // See https://nullprogram.com/blog/2023/01/08/ | |
| static SDL_Window *window=NULL; | |
| static SDL_Surface *surface=NULL; | |
| static int keystate[256]={0}; | |
| int init(int w,int h,int mode) | |
| { | |
| const Uint32 styles[]={SDL_WINDOW_BORDERLESS,0,SDL_WINDOW_RESIZABLE,SDL_WINDOW_FULLSCREEN_DESKTOP}; | |
| if(SDL_Init(SDL_INIT_VIDEO)<0) | |
| { | |
| SDL_Log("SDL_Init failed: %s\n",SDL_GetError()); | |
| return 0; | |
| } | |
| width=w; height=h; | |
| if(!(window=SDL_CreateWindow("demo", | |
| SDL_WINDOWPOS_UNDEFINED,SDL_WINDOWPOS_UNDEFINED,width,height, | |
| styles[mode]))) | |
| { | |
| SDL_Log("SDL_CreateWindow failed: %s\n",SDL_GetError()); | |
| return 0; | |
| } | |
| surface=SDL_CreateRGBSurfaceWithFormat(0,width,height,32,SDL_PIXELFORMAT_RGBA32); | |
| SDL_SetSurfaceBlendMode(surface,SDL_BLENDMODE_NONE); | |
| colorbuffer=(unsigned char*)surface->pixels; | |
| return 1; | |
| } | |
| int frame(void) | |
| { | |
| int i,quit=0,w,h; | |
| SDL_Event e; | |
| SDL_BlitSurface(surface,NULL,SDL_GetWindowSurface(window),NULL); | |
| SDL_UpdateWindowSurface(window); | |
| for(i=0;i<256;++i) keystate[i]=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; | |
| } | |
| mouse_b=(int)(SDL_GetMouseState(&mouse_x,&mouse_y)/SDL_BUTTON(1))&7; | |
| SDL_GetWindowSize(window,&w,&h); | |
| if(w!=width||h!=height) | |
| { | |
| SDL_FreeSurface(surface); | |
| if(!(surface=SDL_CreateRGBSurfaceWithFormat(0,width=w,height=h,32,SDL_PIXELFORMAT_RGBA32))) return 0; | |
| SDL_SetSurfaceBlendMode(surface,SDL_BLENDMODE_NONE); | |
| colorbuffer=(unsigned char*)surface->pixels; | |
| } | |
| return !quit; | |
| } | |
| static double get_time_in_seconds(void) {return 0.001*(double)SDL_GetTicks();} // Or SDL_GetTicks64(). | |
| static void set_window_title(const char *title) {SDL_SetWindowTitle(window,title);} | |
| static void sleep(double time_in_seconds) {SDL_Delay((Uint32)(1000.0*time_in_seconds+0.5));} | |
| int main(int argc,char **argv) | |
| { | |
| int ret=entrypoint(argc,argv); | |
| if(surface) SDL_FreeSurface(surface); | |
| if(window) SDL_DestroyWindow(window); | |
| SDL_Quit(); | |
| return ret; | |
| } | |
| #else // WinAPI backend. Can be forced via -DUSE_SDL=0 option. | |
| #include "windows.h" | |
| static HWND hwnd=NULL; | |
| static HINSTANCE hinst=NULL; | |
| int init(int w,int h,int mode) | |
| { | |
| WNDCLASS wc={0}; // No need for extended window class/style. | |
| RECT rect={0}; | |
| DWORD styles[]={WS_POPUP,WS_POPUP|WS_CAPTION|WS_SYSMENU|WS_MINIMIZEBOX,WS_OVERLAPPEDWINDOW,WS_POPUP|WS_CLIPSIBLINGS|WS_CLIPCHILDREN}; | |
| DWORD style=styles[mode]; | |
| wc.lpfnWndProc=DefWindowProcA; // No need for custom WndProc. | |
| wc.hInstance =hinst; | |
| wc.hCursor =LoadCursor(NULL,IDC_ARROW); // hIcon is fine being NULL, but not hCursor. | |
| wc.lpszClassName="Demo"; | |
| if(!RegisterClassA(&wc)) return 0; | |
| if(mode==3) {w=GetSystemMetrics(SM_CXSCREEN); h=GetSystemMetrics(SM_CYSCREEN);} | |
| rect.right=(width=w); rect.bottom=(height=h); | |
| 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*(mode!=3),CW_USEDEFAULT*(mode!=3),rect.right-rect.left,rect.bottom-rect.top, | |
| NULL,NULL,hinst,NULL))) return 0; | |
| if(!(colorbuffer=(unsigned char*)LocalAlloc(LPTR,(SIZE_T)(width*height*4*2)))) return 0; | |
| return 1; | |
| } | |
| static int frame(void) | |
| { | |
| BITMAPINFOHEADER bmih={sizeof(BITMAPINFOHEADER),width,-height,1,32,BI_RGB,0,0,0,0,0}; | |
| HDC hdc=NULL; | |
| MSG msg; | |
| int x,y,i,quit=0; | |
| RECT rect={0}; | |
| POINT cur={0}; | |
| 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(!(hdc=GetDC(hwnd))) return 0; | |
| 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); | |
| 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); | |
| GetCursorPos(&cur); | |
| ScreenToClient(hwnd,&cur); | |
| mouse_x=cur.x; | |
| mouse_y=cur.y; | |
| mouse_b=((GetAsyncKeyState(VK_LBUTTON)>>15)&1)+((GetAsyncKeyState(VK_MBUTTON)>>15)&2)+((GetAsyncKeyState(VK_RBUTTON)>>15)&4); | |
| GetClientRect(hwnd,&rect); | |
| if(rect.right-rect.left!=width||rect.bottom-rect.top!=height) | |
| { | |
| width=rect.right-rect.left; height=rect.bottom-rect.top; | |
| LocalFree(colorbuffer); | |
| if(!(colorbuffer=(unsigned char*)LocalAlloc(LPTR,(SIZE_T)(width*height*4*2)))) return 0; | |
| } | |
| return !quit; | |
| } | |
| static double get_time_in_seconds(void) | |
| { | |
| static double f=-1.0; | |
| LARGE_INTEGER c={0}; | |
| if(f<0.0) {QueryPerformanceFrequency(&c); f=(double)c.QuadPart;} | |
| QueryPerformanceCounter(&c); | |
| return (double)c.QuadPart/f; | |
| } | |
| static void set_window_title(const char *title) {SetWindowTextA(hwnd,title);} | |
| static void sleep(double time_in_seconds) {Sleep((DWORD)(1000.0*time_in_seconds+0.5));} | |
| int WINAPI WinMain(HINSTANCE hInstance,HINSTANCE hPrevInstance,LPSTR lpCmdLine,int nCmdShow) | |
| { | |
| // Based on https://stackoverflow.com/questions/74999026/is-there-the-commandlinetoargva-function-in-windows-c-c-vs-2022 | |
| int argc=0; | |
| char **argv=0,*p; | |
| LPWSTR *argvw=CommandLineToArgvW(GetCommandLineW(),&argc); | |
| int i,len=0,ret=0; | |
| (void)hPrevInstance; (void)lpCmdLine; (void)nCmdShow; | |
| hinst=hInstance; | |
| for(i=0;i<argc;++i) | |
| len+=WideCharToMultiByte(CP_UTF8,0,argvw[i],-1,NULL,0,NULL,NULL)+1; | |
| argv=(char**)LocalAlloc(LPTR,(SIZE_T)(argc+1)*sizeof(char*)+(SIZE_T)(len)); | |
| p=(char*)&(argv[argc+1]); | |
| for(i=0;i<argc;++i) | |
| { | |
| argv[i]=p; | |
| p+=WideCharToMultiByte(CP_UTF8,0,argvw[i],-1,p,len,NULL,NULL)+1; | |
| } | |
| argv[argc]=NULL; | |
| LocalFree(argvw); | |
| ret=entrypoint(argc,argv); | |
| if(colorbuffer) LocalFree(colorbuffer); | |
| if(hwnd) DestroyWindow(hwnd); | |
| return ret; | |
| } | |
| #endif | |
| #include <stdio.h> | |
| #include <string.h> | |
| int entrypoint(int argc,char **argv) | |
| { | |
| int w=640,h=480,mode=1,frames=0,o[2],i; | |
| double prv,cur,spf=0.0; | |
| for(i=1;i<argc;++i) | |
| { | |
| if(!strcmp(argv[i],"--borderless")) mode=0; | |
| if(!strcmp(argv[i],"--default")) mode=1; | |
| if(!strcmp(argv[i],"--resizable")) mode=2; | |
| if(!strcmp(argv[i],"--fullscreen")) mode=3; | |
| if(sscanf(argv[i],"--size=%dx%d",o+0,o+1)==2) {w=o[0]; h=o[1];} | |
| } | |
| if(!init(w,h,mode)) return 1; | |
| prv=get_time_in_seconds(); | |
| while(frame()) | |
| { | |
| int x,y,i; | |
| char buf[256]={0}; | |
| ++frames; | |
| if(keys[27]||keys['Q']) break; | |
| if((cur=get_time_in_seconds())-prv>=0.25) {spf=(cur-prv)/(double)frames; prv=cur; frames=0;} | |
| sprintf(buf,"%dx%d %7.2f ms/frame",width,height,1000.0*spf); | |
| set_window_title(buf); | |
| sleep(0.001); | |
| 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)((0xFF77AA22u>>(8*i))+((x^y)&16)+(unsigned)((x-mouse_x)*(x-mouse_x)+(y-mouse_y)*(y-mouse_y)<100?20+10*mouse_b:0)); | |
| } | |
| return 0; | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment