{"id":20950443,"url":"https://github.com/colleagueriley/software-rendering","last_synced_at":"2025-04-11T17:34:35.186Z","repository":{"id":253804123,"uuid":"844314553","full_name":"ColleagueRiley/Software-Rendering","owner":"ColleagueRiley","description":"A tutorial that explains how to setup and handle software rendering for X11, WinAPI and Cocoa.","archived":false,"fork":false,"pushed_at":"2024-08-19T17:17:43.000Z","size":94,"stargazers_count":4,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-03-25T13:39:24.009Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","language":"C","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/ColleagueRiley.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2024-08-19T01:59:30.000Z","updated_at":"2025-02-07T02:34:08.000Z","dependencies_parsed_at":"2024-08-20T23:34:00.995Z","dependency_job_id":null,"html_url":"https://github.com/ColleagueRiley/Software-Rendering","commit_stats":null,"previous_names":["colleagueriley/software-rendering"],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ColleagueRiley%2FSoftware-Rendering","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ColleagueRiley%2FSoftware-Rendering/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ColleagueRiley%2FSoftware-Rendering/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ColleagueRiley%2FSoftware-Rendering/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/ColleagueRiley","download_url":"https://codeload.github.com/ColleagueRiley/Software-Rendering/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248450282,"owners_count":21105655,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-11-19T00:48:36.052Z","updated_at":"2025-04-11T17:34:35.168Z","avatar_url":"https://github.com/ColleagueRiley.png","language":"C","funding_links":[],"categories":[],"sub_categories":[],"readme":"# RGFW Under the Hood: Software Rendering\n## Introduction\nRGFW is a lightweight single-header windowing library, its source code can be found [here](https://github.com/ColleagueRiley/RGFW). \nThis tutorial is based on its source code.\n\nThe basic idea of software rendering is simple. It comes down to drawing to a buffer and blitting it to the screen.\nHowever, software rendering is more complicated when working with low-level APIs because you must \nproperly initialize a rendering context, telling the API how to expect the data. Then to draw you have to use the API's functions to\nblit to the screen, which can be complicated. \n\nThis tutorial explains how RGFW handles software rendering so you can understand how to implement it yourself.\n\nNOTE: MacOS code will be written with a Cocoa C Wrapper in mind (see the RGFW.h or Silicon.h)\n\n## Overview\n\nA quick overview of the steps required\n\n1. Initialize buffer and rendering context\n2. Draw to the buffer\n3. Blit buffer to the screen \n4. Free leftover data\n\n## Step 1 (Initialize buffer and rendering context)\n\nNOTE: You may want the buffer's size to be bigger than the window so you can scale the buffer's size without reallocating it. \n\nOn X11 you start by creating a Visual (or pixel format) that tells the window how to handle the draw data.\nThen create a bitmap for the buffer to render with, RGFW uses an XImage structure for the bitmap. \nNext, you create a Graphics Context (GC) using the display and window data. The GC is used to tell X11 how to give \nthe window its draw data. \n\nThis is also where you can allocate the buffer. The buffer must be allocated for each platform except for Windows. \n\nFor this you need to use, [`XMatchVisualInfo`](https://tronche.com/gui/x/xlib/utilities/XMatchVisualInfo.html), [`XCreateImage`](https://tronche.com/gui/x/xlib/utilities/XCreateImage.html), and [`XCreateGC`](https://www.x.org/releases/X11R7.5/doc/man/man3/XFreeGC.3.html)\n\n```c\nXVisualInfo vi;\nvi.visual = DefaultVisual(display, DefaultScreen(display));\n\t\t\nXMatchVisualInfo(display, DefaultScreen(display), 32, TrueColor, \u0026vi);\n\nXImage* bitmap = XCreateImage(\n\t\t\tdisplay, XDefaultVisual(display, vi.screen),\n\t\t\tvi.depth,\n\t\t\tZPixmap, 0, NULL, RGFW_bufferSize.w, RGFW_bufferSize.h,\n\t\t    \t32, 0\n);\n\n/* ..... */\n/* Now this visual can be used to create a window and colormap */\n\nXSetWindowAttributes swa;\nColormap cmap;\n\nswa.colormap = cmap = XCreateColormap((Display*) display, DefaultRootWindow(display), vi.visual, AllocNone);\n\nswa.background_pixmap = None;\nswa.border_pixel = 0;\nswa.event_mask = event_mask;\n\nswa.background_pixel = 0;\n\nWindow window = XCreateWindow((Display*) display, DefaultRootWindow((Display*) display), x, y, w, h,\n\t\t\t\t0, vi.depth, InputOutput, vi.visual,\n\t\t\t\tCWColormap | CWBorderPixel | CWBackPixel | CWEventMask, \u0026swa);\n/* .... */\n\nGC gc = XCreateGC(display, window, 0, NULL);\n\nu8* buffer = (u8*)malloc(RGFW_bufferSize.w * RGFW_bufferSize.h * 4);\n```\n\nOn Windows, you'll start by creating a bitmap header, which is used to create a bitmap with a specified format.\nThe format structure is used to tell the Windows API how to render the buffer to the screen.\n\nNext, you create a Drawing Context Handle (HDC) allocated in memory, this is used for selecting the bitmap later.\n\nNOTE: Windows does not need to allocate a buffer because Winapi handles that memory for us. You can also allocate the memory by hand. \n\nRelevant Documentation: [`BITMAPV5HEADER`](bitmapv5header), [`CreateDIBSection`](https://learn.microsoft.com/en-us/windows/win32/api/wingdi/nf-wingdi-createdibsection) and [`CreateCompatibleDC`](https://learn.microsoft.com/en-us/windows/win32/api/wingdi/nf-wingdi-createcompatibledc)\n\n```c\nBITMAPV5HEADER bi;\nZeroMemory(\u0026bi, sizeof(bi));\nbi.bV5Size = sizeof(bi);\nbi.bV5Width = RGFW_bufferSize.w;\nbi.bV5Height = -((LONG) RGFW_bufferSize.h);\nbi.bV5Planes = 1;\nbi.bV5BitCount = 32;\nbi.bV5Compression = BI_BITFIELDS;\n\n// where it can expect to find the RGBA data\n// (note: this might need to be changed according to the endianness) \nbi.bV5BlueMask = 0x00ff0000;\nbi.bV5GreenMask = 0x0000ff00;\nbi.bV5RedMask = 0x000000ff;\nbi.bV5AlphaMask = 0xff000000;\n\nu8* buffer;\n\nHBITMAP bitmap = CreateDIBSection(hdc,\n\t(BITMAPINFO*) \u0026bi,\n\tDIB_RGB_COLORS,\n\t(void**) \u0026buffer,\n\tNULL,\n\t(DWORD) 0);\n\nHDC hdcMem = CreateCompatibleDC(hdc);\n```\n\nOn MacOS, there is not much setup, most of the work is done during rendering. \n\nYou only need to allocate the buffer data.\n```c\nu8* buffer = malloc(RGFW_bufferSize.w * RGFW_bufferSize.h * 4);\n```\n\n## Step 2 (Draw to the buffer)\nFor this tutorial, I will use [Silk.h](https://github.com/itsYakub/Silk/) for drawing to the buffer. Silk.h is a single-header software rendering graphics library.\n\n\nFirst, include silk, \n\n```c\n#define SILK_PIXELBUFFER_WIDTH w\n#define SILK_PIXELBUFFER_HEIGHT h\n#define SILK_IMPLEMENTATION\n#include \"silk.h\"\n```\n\nNow you can render using silk.\n\n```c\nsilkClearPixelBufferColor((pixel*)buffer, 0x11AA0033);\n\nsilkDrawCircle(\n            (pixel*)buffer, \n            (vec2i) { SILK_PIXELBUFFER_WIDTH, SILK_PIXELBUFFER_HEIGHT },\n            SILK_PIXELBUFFER_WIDTH,\n            (vec2i) { SILK_PIXELBUFFER_CENTER_X, SILK_PIXELBUFFER_CENTER_Y - 60}, \n            60,\n            0xff0000ff\n);\n```\n\n## Step 3 (Blit the buffer to the screen)\n\nOn X11, you first set the bitmap data to the buffer.\nThe bitmap data will be rendered using BGR, so you must  \nconvert the data if you want to use RGB. Then you'll have to use `XPutImage`\nto draw the XImage to the window using the GC.\n\nRelevant documentation: [`XPutImage`](https://tronche.com/gui/x/xlib/graphics/XPutImage.html)\n\n```c\nbitmap-\u003edata = (char*) buffer;\n#ifndef RGFW_X11_DONT_CONVERT_BGR\n\tu32 x, y;\n\tfor (y = 0; y \u003c (u32)window_height; y++) {\n\t\tfor (x = 0; x \u003c (u32)window_width; x++) {\n\t\t\tu32 index = (y * 4 * area.w) + x * 4;\n\n\t\t\tu8 red = bitmap-\u003edata[index];\n\t\t\tbitmap-\u003edata[index] = buffer[index + 2];\n\t\t\tbitmap-\u003edata[index + 2] = red;\n\t\t}\n    }\n#endif\t\nXPutImage(display, (Window)window, gc, bitmap, 0, 0, 0, 0, RGFW_bufferSize.w, RGFW_bufferSize.h);\n```\n\n\nOn Windows, you must first select the bitmap and make sure that you save the last selected object so you can reselect it later.\nNow you can blit the bitmap to the screen and reselect the old bitmap. \n\nRelevant documentation: [`SelectObject`](https://learn.microsoft.com/en-us/windows/win32/api/wingdi/nf-wingdi-selectobject) and [`BitBlt`](https://learn.microsoft.com/en-us/windows/win32/api/wingdi/nf-wingdi-bitblt)\n\n```c\nHGDIOBJ oldbmp = SelectObject(hdcMem, bitmap);\nBitBlt(hdc, 0, 0, window_width, window_height, hdcMem, 0, 0, SRCCOPY);\nSelectObject(hdcMem, oldbmp);\n```\n\nOn MacOS, set the view's CALayer according to your window, this is used for rendering the image to the screen. \nNext, create the image (bitmap) using the buffer. \nFinally, you can add the image to the layer's graphics context, and draw and flush the layer to the screen. \n\nRelevant documentation: [`CGColorSpaceCreateDeviceRGB`](https://developer.apple.com/documentation/coregraphics/1408837-cgcolorspacecreatedevicergb), [`CGBitmapContextCreate`](https://developer.apple.com/documentation/coregraphics/1455939-cgbitmapcontextcreate),  [`CGBitmapContextCreateImage`](https://developer.apple.com/documentation/coregraphics/1454225-cgbitmapcontextcreateimage), [`CGColorSpaceRelease`](https://developer.apple.com/documentation/coregraphics/1408855-cgcolorspacerelease), [`CGContextRelease`](https://developer.apple.com/documentation/coregraphics/1586509-cgcontextrelease),\n[`CALayer`](https://developer.apple.com/documentation/quartzcore/calayer?ref=weekly.elfitz.com), [`NSGraphicsContext`](https://developer.apple.com/documentation/appkit/nsgraphicscontext), [`CGContextDrawImage`](https://developer.apple.com/documentation/coregraphics/1454845-cgcontextdrawimage), [`flushGraphics`](https://developer.apple.com/documentation/appkit/nsgraphicscontext/1527919-flushgraphics) and, [`CGImageRelease`](https://developer.apple.com/documentation/coregraphics/1556742-cgimagerelease)\n```c\nCGImageRef createImageFromBytes(unsigned char *buffer, int width, int height) {\n\t// Define color space\n\tCGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();\n\t// Create bitmap context\n\tCGContextRef context = CGBitmapContextCreate(\n\t\t\tbuffer, \n\t\t\twidth, height,\n\t\t\t8,\n\t\t\tRGFW_bufferSize.w * 4, \n\t\t\tcolorSpace,\n\t\t\tkCGImageAlphaPremultipliedLast);\n\t\n\t// Create image from bitmap context\n\tCGImageRef image = CGBitmapContextCreateImage(context);\n\t// Release the color space and context\n\tCGColorSpaceRelease(colorSpace);\n\tCGContextRelease(context);\n\t\t\t \n\treturn image;\n}\n\n...\nvoid* view = NSWindow_contentView(window);\nvoid* layer = objc_msgSend_id(view, sel_registerName(\"layer\"));\n\n((void(*)(id, SEL, NSRect))objc_msgSend)(layer,\n\t\t\t\tsel_registerName(\"setFrame:\"),\n\t\t\t\t(NSRect){{0, 0}, {window_width, window_height}});\n\nCGImageRef image = createImageFromBytes(buffer, window_width, window_height);\n\n// Get the current graphics context\nid graphicsContext = objc_msgSend_class(objc_getClass(\"NSGraphicsContext\"), sel_registerName(\"currentContext\"));\n\n// Get the CGContext from the current NSGraphicsContext\nid cgContext = objc_msgSend_id(graphicsContext, sel_registerName(\"graphicsPort\"));\n\n// Draw the image in the context\nNSRect bounds = (NSRect){{0,0}, {window_width, window_height}};\nCGContextDrawImage((void*)cgContext, *(CGRect*)\u0026bounds, image);\n\n// Flush the graphics context to ensure the drawing is displayed\nobjc_msgSend_id(graphicsContext, sel_registerName(\"flushGraphics\"));\n            \nobjc_msgSend_void_id(layer, sel_registerName(\"setContents:\"), (id)image);\nobjc_msgSend_id(layer, sel_registerName(\"setNeedsDisplay\"));\n            \nCGImageRelease(image);\n```\n## Step 4 (Free leftover data)\n\nWhen you're done rendering, you should free the bitmap and image data using the respective API functions.\n\nOn X11 and MacOS, you also should free the buffer.\n\nOn X11 you must use [`XDestoryImage`](https://tronche.com/gui/x/xlib/utilities/XDestroyImage.html) and [`XFreeGC`](https://tronche.com/gui/x/xlib/GC/XFreeGC.html).\n```c\nXDestroyImage(bitmap);\nXFreeGC(display, gc);\nfree(buffer);\n```\n\nOn Windows, you must use [`DeleteDC`](https://learn.microsoft.com/en-us/windows/win32/api/wingdi/nf-wingdi-deletedc) and [`DeleteObject`](https://learn.microsoft.com/en-us/windows/win32/api/wingdi/nf-wingdi-deleteobject).\n```c\nDeleteDC(hdcMem);\nDeleteObject(bitmap);\n```\n\nOn MacOS you must use [`release`](https://developer.apple.com/documentation/objectivec/1418956-nsobject/1571957-release).\n\n```c\nrelease(bitmap);\nrelease(image);\nfree(buffer);\n```\n\n\n## full examples\n\n## X11\n\n```c\n// This can be compiled with \n// gcc x11.c -lX11 -lm\n\n#include \u003cX11/Xlib.h\u003e\n#include \u003cX11/Xutil.h\u003e\n\n#include \u003cstdio.h\u003e\n#include \u003cstdlib.h\u003e\n\n\n#define SILK_PIXELBUFFER_WIDTH 500\n#define SILK_PIXELBUFFER_HEIGHT 500\n#define SILK_IMPLEMENTATION\n#include \"silk.h\"\n\nint main() {\n\tDisplay* display = XOpenDisplay(NULL);\n\tXVisualInfo vi;\n\tvi.visual = DefaultVisual(display, DefaultScreen(display));\n\t\t\n\tXMatchVisualInfo(display, DefaultScreen(display), 32, TrueColor, \u0026vi);\n\n\tXImage* bitmap = XCreateImage(\n\t\t\tdisplay, XDefaultVisual(display, vi.screen),\n\t\t\tvi.depth,\n\t\t\tZPixmap, 0, NULL, 500, 500,\n\t\t    32, 0\n\t);\n\n\t/* ..... */\n\t/* Now this visual can be used to create a window and colormap */\n\t\n\tXSetWindowAttributes swa;\n\tColormap cmap;\n\n\tswa.colormap = cmap = XCreateColormap((Display*) display, DefaultRootWindow(display), vi.visual, AllocNone);\n\n\tswa.background_pixmap = None;\n\tswa.border_pixel = 0;\n\tswa.event_mask = CWColormap | CWBorderPixel | CWBackPixel | CWEventMask;\n\n\tswa.background_pixel = 0;\n\n\tWindow window = XCreateWindow((Display*) display, DefaultRootWindow((Display*) display), 500, 500, 500, 500,\n\t\t\t\t\t0, vi.depth, InputOutput, vi.visual,\n\t\t\t\t\tCWColormap | CWBorderPixel | CWBackPixel | CWEventMask, \u0026swa);\n\t/* .... */\n\n\tGC gc = XCreateGC(display, window, 0, NULL);\n\n\tu8* buffer = (u8*)malloc(500 * 500 * 4);\n\n\tXSelectInput(display, window, ExposureMask | KeyPressMask);\n\tXMapWindow(display, window);\n\n\tXEvent event;\n\tfor (;;) {\n\t\tXNextEvent(display, \u0026event);\n\t\t\n\t\tsilkClearPixelBufferColor((pixel*)buffer, 0x11AA0033);\n\n\t\tsilkDrawCircle(\n\t\t\t\t(pixel*)buffer, \n\t\t\t\t(vec2i) { SILK_PIXELBUFFER_WIDTH, SILK_PIXELBUFFER_HEIGHT },\n\t\t\t\tSILK_PIXELBUFFER_WIDTH,\n\t\t\t\t(vec2i) { SILK_PIXELBUFFER_CENTER_X, SILK_PIXELBUFFER_CENTER_Y - 60}, \n\t\t\t\t60,\n\t\t\t\t0xff0000ff\n\t\t);\n\n\t\tbitmap-\u003edata = (char*) buffer;\n\t\t#ifndef RGFW_X11_DONT_CONVERT_BGR\n\t\t\tu32 x, y;\n\t\t\tfor (y = 0; y \u003c (u32)500; y++) {\n\t\t\t\tfor (x = 0; x \u003c (u32)500; x++) {\n\t\t\t\t\tu32 index = (y * 4 * 500) + x * 4;\n\t\t\n\t\t\t\t\tu8 red = bitmap-\u003edata[index];\n\t\t\t\t\tbitmap-\u003edata[index] = buffer[index + 2];\n\t\t\t\t\tbitmap-\u003edata[index + 2] = red;\n\t\t\t\t}\n\t\t\t}\n\t\t#endif\t\n\t\tXPutImage(display, (Window) window, gc, bitmap, 0, 0, 0, 0, 500, 500);\n\t}\n\n\tXDestroyImage(bitmap);\n\tXFreeGC(display, gc);\n\tfree(buffer);\n}\n```\n\n## windows\n\n```c\n// This can be compiled with\n// gcc win32.c -lgdi32 -lm\n\n#include \u003cwindows.h\u003e\n\n#include \u003cstdio.h\u003e\n#include \u003cstdint.h\u003e\n#include \u003cassert.h\u003e\n\n#define SILK_PIXELBUFFER_WIDTH 500\n#define SILK_PIXELBUFFER_HEIGHT 500\n#define SILK_IMPLEMENTATION\n#include \"silk.h\"\n\nint main() {\n\tWNDCLASS wc = {0};\n\twc.lpfnWndProc   = DefWindowProc; // Default window procedure\n\twc.hInstance     = GetModuleHandle(NULL);\n\twc.lpszClassName = \"SampleWindowClass\";\n\t\n\tRegisterClass(\u0026wc);\n\t\n\tHWND hwnd = CreateWindowA(wc.lpszClassName, \"Sample Window\", 0,\n\t\t\t500, 500, 500, 500,\n\t\t\tNULL, NULL, wc.hInstance, NULL);\n\n\n\tBITMAPV5HEADER bi = { 0 };\n\tZeroMemory(\u0026bi, sizeof(bi));\n\tbi.bV5Size = sizeof(bi);\n\tbi.bV5Width = 500;\n\tbi.bV5Height = -((LONG) 500);\n\tbi.bV5Planes = 1;\n\tbi.bV5BitCount = 32;\n\tbi.bV5Compression = BI_BITFIELDS;\n\n    \t// where it can expect to find the RGB data\n\t// (note: this might need to be changed according to the endianness) \n\tbi.bV5BlueMask = 0x00ff0000;\n\tbi.bV5GreenMask = 0x0000ff00;\n\tbi.bV5RedMask = 0x000000ff;\n\tbi.bV5AlphaMask = 0xff000000;\n\t\n\tu8* buffer;\n\t\n\tHDC hdc = GetDC(hwnd); \n\tHBITMAP bitmap = CreateDIBSection(hdc,\n\t\t(BITMAPINFO*) \u0026bi,\n\t\tDIB_RGB_COLORS,\n\t\t(void**) \u0026buffer,\n\t\tNULL,\n\t\t(DWORD) 0);\n\t\n\tHDC hdcMem = CreateCompatibleDC(hdc);\t\n\t\n\tShowWindow(hwnd, SW_SHOW);\n\tUpdateWindow(hwnd);\n\t\n\tMSG msg;\n\t\n\tBOOL running = TRUE;\n\t\n\twhile (running) {\n\t\tif (PeekMessageA(\u0026msg, hwnd, 0u, 0u, PM_REMOVE)) {\n\t\t\tTranslateMessage(\u0026msg);\n\t\t\tDispatchMessage(\u0026msg);\n\t\t}\n\t\t\n\t\trunning = IsWindow(hwnd);\n\t\t\n\t\tsilkClearPixelBufferColor((pixel*)buffer, 0x11AA0033);\n\t\t\n\t\tsilkDrawCircle(\n\t\t\t(pixel*)buffer, \n\t\t\t(vec2i) { SILK_PIXELBUFFER_WIDTH, SILK_PIXELBUFFER_HEIGHT },\n\t\t\tSILK_PIXELBUFFER_WIDTH,\n\t\t\t(vec2i) { SILK_PIXELBUFFER_CENTER_X, SILK_PIXELBUFFER_CENTER_Y - 60}, \n\t\t\t60,\n\t\t\t0xff0000ff\n\t\t);\n\t\t\n\t\tHGDIOBJ oldbmp = SelectObject(hdcMem, bitmap);\n\t\tBitBlt(hdc, 0, 0, 500, 500, hdcMem, 0, 0, SRCCOPY);\n\t\tSelectObject(hdcMem, oldbmp);\n\t}\n\t\n\tDeleteDC(hdcMem);\n\tDeleteObject(bitmap);\n\treturn 0;\n}\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcolleagueriley%2Fsoftware-rendering","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fcolleagueriley%2Fsoftware-rendering","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcolleagueriley%2Fsoftware-rendering/lists"}