SimplifyC++ Article
Graphics Programming Using the CPU Only (No GPU) A Complete Guide for Professionals on Linux and Windows
Graphics Programming Using the CPU Only (No GPU): A Complete Guide for Professionals on Linux and Windows
Why Program Graphics Without a GPU?
With the advancement of GPU technologies, some might wonder: why would anyone be interested in drawing graphics using only the CPU? Yet, there are solid reasons, including:
Developing custom graphical operating systems or emulators.
Creating diagnostic or educational tools.
Learning the core of graphics programming from the ground up.
Working in limited environments without GPU access, such as embedded systems.
This guide explains how to build basic graphics routines relying entirely on the CPU, without OpenGL, DirectX, or any GPU acceleration, and it covers both Linux and Windows systems.
Fundamental Concept
The core idea is that the screen is essentially a linear memory area (called a framebuffer) where each pixel has its place. If you can write into this memory correctly, you can control what appears on the screen.
Steps:
Access the framebuffer through OS services or memory-mapped files.
Calculate the pixel’s position in memory.
Determine the pixel’s color in the expected format.
Write the pixel color directly to the framebuffer.
CPU-Based Graphics Programming on Linux
Using /dev/fb0 Framebuffer Device
On Linux systems with framebuffer support, the framebuffer is usually exposed as /dev/fb0, a device that maps directly to the display memory.
Steps for Drawing Using Framebuffer
Open the framebuffer:
int fbfd = open("/dev/fb0", O_RDWR);Get screen information:
struct fb_var_screeninfo vinfo;ioctl(fbfd, FBIOGET_VSCREENINFO, &vinfo);Calculate the screen size in bytes:
int screensize = vinfo.yres_virtual * vinfo.xres_virtual * vinfo.bits_per_pixel / 8;Map the framebuffer into your process’s memory:
char* fbp = (char*) mmap(0, screensize, PROT_READ | PROT_WRITE, MAP_SHARED, fbfd, 0);Calculate a pixel location:
int x = 100, y = 150;int location = (x + vinfo.xoffset) * (vinfo.bits_per_pixel/8) + (y + vinfo.yoffset) * vinfo.line_length;Set the pixel color to red (in 24-bit color):
fbp[location] = 0x00; // Bluefbp[location+1] = 0x00; // Greenfbp[location+2] = 0xFF; // RedClean up:
munmap(fbp, screensize);close(fbfd);
Full Example: Draw a Red Square on Linux
int main() { int fb = open("/dev/fb0", O_RDWR); if (fb < 0) return 1;
struct fb_var_screeninfo vinfo; ioctl(fb, FBIOGET_VSCREENINFO, &vinfo);
long screensize = vinfo.yres_virtual * vinfo.xres_virtual * vinfo.bits_per_pixel / 8; char* fbp = mmap(0, screensize, PROT_READ | PROT_WRITE, MAP_SHARED, fb, 0);
for (int y = 100; y < 200; y++) { for (int x = 100; x < 200; x++) { long location = (x + vinfo.xoffset) * (vinfo.bits_per_pixel/8) + (y + vinfo.yoffset) * vinfo.line_length;
fbp[location] = 0x00; // Blue fbp[location+1] = 0x00; // Green fbp[location+2] = 0xFF; // Red if (vinfo.bits_per_pixel == 32) fbp[location+3] = 0; } }
munmap(fbp, screensize); close(fb); return 0;}
CPU-Based Graphics Programming on Windows
Direct access to video memory is not allowed on modern versions of Windows, but graphics can still be drawn using Windows GDI (Graphics Device Interface) by preparing a memory buffer and displaying it using Windows API.
Recommended Approach Using WinAPI
Create a window using the Windows API.
Prepare a raw pixel buffer in memory.
Draw into the buffer manually.
Display the buffer using
StretchDIBits()or similar.
Full Example: Drawing a Red Square Using GDI
unsigned char buffer[WIDTH * HEIGHT * 4];
void DrawPixel(int x, int y, unsigned char r, unsigned char g, unsigned char b) { int offset = (y * WIDTH + x) * 4; buffer[offset] = b; buffer[offset + 1] = g; buffer[offset + 2] = r; buffer[offset + 3] = 0;}
LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) { if (msg == WM_PAINT) { PAINTSTRUCT ps; HDC hdc = BeginPaint(hwnd, &ps);
BITMAPINFO bmi = {0}; bmi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER); bmi.bmiHeader.biWidth = WIDTH; bmi.bmiHeader.biHeight = -HEIGHT; bmi.bmiHeader.biPlanes = 1; bmi.bmiHeader.biBitCount = 32; bmi.bmiHeader.biCompression = BI_RGB;
StretchDIBits(hdc, 0, 0, WIDTH, HEIGHT, 0, 0, WIDTH, HEIGHT, buffer, &bmi, DIB_RGB_COLORS, SRCCOPY);
EndPaint(hwnd, &ps); return 0; } else if (msg == WM_DESTROY) { PostQuitMessage(0); return 0; } return DefWindowProc(hwnd, msg, wParam, lParam);}
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrev, LPSTR lpCmd, int nCmdShow) { WNDCLASS wc = {0}; wc.lpfnWndProc = WndProc; wc.hInstance = hInstance; wc.lpszClassName = "MyWindowClass"; RegisterClass(&wc);
HWND hwnd = CreateWindow("MyWindowClass", "CPU Graphics", WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, WIDTH, HEIGHT, NULL, NULL, hInstance, NULL); ShowWindow(hwnd, nCmdShow);
for (int y = 200; y < 280; y++) for (int x = 280; x < 360; x++) DrawPixel(x, y, 255, 0, 0);
MSG msg = {0}; while (GetMessage(&msg, NULL, 0, 0)) { TranslateMessage(&msg); DispatchMessage(&msg); }
return 0;}
Relevant Assembly Instructions for Pixel-Level Graphics
| Category | Instructions | Purpose |
|---|---|---|
| Memory Access | MOV, STOSB, REP STOSB | Write color values to framebuffer |
| Arithmetic | IMUL, ADD, LEA, SHL | Calculate memory offset for each pixel |
| Looping | LOOP, JNZ, JMP | Iterate over pixel rows and columns |
| Logic | AND, OR, XOR | Pixel blending, masks, or color filters |
What Projects Can You Build Without a GPU?
Line drawing (e.g., Bresenham's algorithm).
Image viewers (for BMP or raw formats).
Simple 2D games (Tetris, Snake, etc.).
Mathematical visualizations and fractals.
Basic 3D software rendering (for advanced developers).
Conclusion
Building a graphics engine or drawing directly to the screen using the CPU is a rewarding experience. It teaches the fundamentals of how images are represented in memory, how displays work, and what role the GPU actually plays.
Whether on Linux or Windows, low-level graphics programming enhances your understanding of performance, memory, and rendering principles. While not suitable for heavy or commercial applications today, it is still a valuable learning path for advanced programmers and system developers.
What did you think?
Sign in to react or comment.
Comments
0No comments yet.