OpenGL Implementation
Forest OS includes a complete software OpenGL 1.1 renderer built directly into the kernel. No GPU, graphics card, or hardware acceleration needed — everything runs on the CPU in pure C.
Source:
fern/src/gl/— 69 files implementing the full GL pipeline.
What Is It?
A software rasterizer implementing the OpenGL 1.1 fixed-function pipeline plus extensions. When you call glBegin(), glVertex3f(), or glDrawArrays(), the kernel processes every vertex, rasterizes every triangle, and writes every pixel — all on the CPU.
glGetString(GL_RENDERER); // "Software OpenGL 1.1"
glGetString(GL_VERSION); // "1.1 Forest Software"
Enable with ENABLE_OPENGL=yes in your build config (fern/build/features/opengl.mk).
Why Software Rendering?
Forest OS is a from-scratch operating system without GPU drivers. Software rendering provides:
- Universal compatibility — works on any hardware with a CPU and framebuffer
- No driver dependencies — no GPU-specific code or proprietary blobs
- Deterministic output — same code produces the same pixels everywhere
- Self-contained — the entire renderer is ~7,000 lines of C
The tradeoff is performance, but for UI compositing, simple 3D, or basic games, it's sufficient.
The Rendering Pipeline
Application Code
v
[Vertex Transform] — MVP matrix multiply, perspective divide, viewport
v
[Triangle Setup] — face culling, winding order check
v
[Rasterization] — 4x4 block scanline rasterizer
v
[Fragment Shader] — texture sampling, lighting, fog, alpha test
v
[Per-Pixel Tests] — depth, stencil, scissor, blending
v
[Framebuffer Write] — color/depth/stencil buffer output
v
[Present to Screen] — format conversion → display
Vertex Transform
Vertices transform from object space to screen space via: Model-View Matrix → Projection Matrix → perspective divide → viewport mapping. The math library (math.h) provides all matrix operations: multiply, translate, rotate, scale, ortho, perspective, lookAt, and invert.
Vertex Array Support
Immediate Mode
glBegin(GL_TRIANGLES);
glColor3f(1.0f, 0.0f, 0.0f); glVertex3f(-1.0f, -1.0f, 0.0f);
glColor3f(0.0f, 1.0f, 0.0f); glVertex3f( 1.0f, -1.0f, 0.0f);
glColor3f(0.0f, 0.0f, 1.0f); glVertex3f( 0.0f, 1.0f, 0.0f);
glEnd();
Vertices buffer up to 4096 per batch, flushed on glEnd().
Vertex Arrays & VBOs
glEnableClientState(GL_VERTEX_ARRAY);
glVertexPointer(3, GL_FLOAT, 0, myVertices);
glDrawArrays(GL_TRIANGLES, 0, count);
// Or with VBOs:
glGenBuffers(1, &vbo);
glBindBuffer(GL_ARRAY_BUFFER, vbo);
glBufferData(GL_ARRAY_BUFFER, size, data, GL_STATIC_DRAW);
Supported arrays: GL_VERTEX_ARRAY, GL_COLOR_ARRAY, GL_NORMAL_ARRAY, GL_TEXTURE_COORD_ARRAY. Data types: float, byte, short, int (signed/unsigned). Up to 256 buffers and 16 vertex attrib pointers.
Display Lists
Record and replay command sequences: glGenLists, glNewList, glEndList, glCallList. Records vertex, color, normal, texcoord, begin/end, enable/disable, bind texture, and matrix operations.
Supported Primitives
GL_POINTS, GL_LINES, GL_LINE_STRIP, GL_LINE_LOOP, GL_TRIANGLES, GL_TRIANGLE_STRIP, GL_TRIANGLE_FAN
Texture Mapping
Up to 256 textures and 8 texture units (multi-texturing).
- Formats: RGBA8, RGB8 (auto-converted to RGBA), Luminance8, Alpha
- Max size: 4096x4096
- Filtering:
GL_NEAREST,GL_LINEAR - Wrapping:
GL_REPEAT,GL_CLAMP_TO_EDGE - Mipmaps: Auto-generated via
glGenerateMipmap()(2x2 box filter down to 1x1) - Combine modes:
GL_MODULATE,GL_REPLACE,GL_ADD,GL_ADD_SIGNED,GL_INTERPOLATE,GL_SUBTRACT,GL_DOT3_RGB,GL_DOT3_RGBA
The Lighting Model
OpenGL 1.1 fixed-function lighting with up to 8 lights, computed per-pixel in the fragment shader.
Each light has: ambient (constant), diffuse (Lambertian N dot L), specular (Phong reflection R dot V raised to shininess), and position (directional if w=0, positional if w=1).
Material properties: ambient, diffuse, specular, and shininess. The normal matrix (inverse-transpose of the upper 3x3 model-view) is computed on-demand for correct handling of non-uniform scaling.
Per-pixel formula:
color = global_ambient * material_ambient
+ sum(light_ambient * material_ambient
+ light_diffuse * material_diffuse * max(N.L, 0)
+ light_specular * material_specular * max(R.V, 0)^shininess)
Shader Support
The shader API exists for compatibility but is a stub:
GLuint vs = glCreateShader(GL_VERTEX_SHADER);
glShaderSource(vs, 1, &source, NULL);
glCompileShader(vs); // always reports success
// ... attach, link, use — falls back to fixed-function pipeline
Shader source is stored but never compiled or executed. Applications using the shader interface won't crash, but always get the default fixed-function behavior. Pool: 64 shaders, 32 programs, 4KB max source.
The Rasterizer
The performance-critical heart of the renderer, with several optimizations:
- Fixed-point arithmetic — 12.4 format avoids float division in the inner loop
- Incremental edge functions — precompute step values, add per pixel/row
- 4x4 block rasterization — cache-line-aligned blocks improve L1 utilization
- Early Z testing — depth test before fragment shader, skipping expensive work on depth-fail
- Perspective-correct interpolation — attributes corrected by clip-space w
- Branchless depth test — ternary chain compiles to
cmovon x86
Also supports line rasterization (Bresenham) and point rasterization.
The Fragment Shader
The default shader (fragment.c) runs per visible pixel:
- Texture sampling — all 8 units, combined via texture environment mode
- Lighting — per-pixel Phong illumination if enabled
- Alpha test — discard fragments based on comparison function
- Fog — blend toward fog color based on distance (linear, exp, or exp2)
The shader is a function pointer (g_gl_fragment_shader) that can be replaced.
Per-Pixel Tests and Blending
| Test | Description |
|---|---|
| Scissor | Clips to rectangular region |
| Alpha | Discards based on alpha comparison |
| Depth | LESS, LEQUAL, GREATER, GEQUAL, EQUAL, NOTEQUAL, ALWAYS, NEVER |
| Stencil | Compare + write operations (keep, zero, replace, incr, decr, invert, wrap) |
| Blending | 10 factors: zero, one, src/dst color/alpha, one-minus variants |
| Logic ops | 16 bitwise operations: AND, OR, XOR, NAND, NOR, INVERT, etc. |
Buffer Layout
Three buffers in RAM:
| Buffer | Type | Per Pixel | 1920x1080 |
|---|---|---|---|
| Color | unsigned int[] |
4 bytes (RGBA8888) | ~8 MB |
| Depth | float[] |
4 bytes | ~8 MB |
| Stencil | unsigned char[] |
1 byte | ~2 MB |
Integration with the Framebuffer
gl_present() copies the GL framebuffer to the screen:
- Gets kernel framebuffer info (width, height, pitch, bpp)
- Converts RGBA to screen format (32-bit BGGA swap, 24-bit RGB888, or 16-bit RGB565)
- Nearest-neighbor scaling if resolutions differ
- Double-buffer present with dirty rect invalidation
gl_present_region() handles partial updates. glBlitFramebuffer() copies between FBOs.
Framebuffer Objects
Offscreen rendering via FBOs: 64 FBOs, 64 renderbuffers. Color and depth attachments supported. Functions: glGenFramebuffers, glBindFramebuffer, glFramebufferTexture2D, glFramebufferRenderbuffer, glCheckFramebufferStatus, glBlitFramebuffer.
Performance Characteristics
What affects speed: triangle count (O(area) per triangle), resolution (linear scaling), texture filtering, per-pixel lighting, overdraw.
What's fast: early Z, 4x4 blocks, branchless depth test, incremental edge functions, fixed-point math.
Rough numbers (x86 at 3 GHz):
- 500K–1M triangles/sec
- 10–30 FPS at 1024x768 for modest 3D scenes
- 30+ FPS for UI/2D graphics
Built-in stats tracking (stats.h): triangle count, pixel count, depth pass/fail, block skip rate.
Limitations vs Hardware OpenGL
| Feature | Hardware GL | Forest GL |
|---|---|---|
| Programmable shaders | Full | Stub only |
| Geometry/tessellation shaders | Yes | No |
| Compute shaders | Yes | No |
| Texture max size | 16K+ | 4096 |
| Texture formats | Dozens | RGBA8, RGB8, Luminance8 |
| Anisotropic filtering | Yes | No |
| MSAA | Yes | No |
| Instanced rendering | Yes | No |
| Transform feedback | Yes | No |
| Multiple render targets | Yes | Single |
| Performance | Millions/tri frame | Thousands–low millions/sec |
Quick Start
A minimal example that draws a colored triangle:
#include "gl.h"
void render_frame(void) {
gl_init_with_framebuffer();
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
glOrtho(0, 800, 600, 0, -1, 1);
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
glBegin(GL_TRIANGLES);
glColor3f(1.0f, 0.0f, 0.0f); glVertex2f(100, 100);
glColor3f(0.0f, 1.0f, 0.0f); glVertex2f(400, 500);
glColor3f(0.0f, 0.0f, 1.0f); glVertex2f(700, 100);
glEnd();
gl_present();
}
Call gl_init_with_framebuffer() once at startup. It queries the kernel framebuffer dimensions, allocates the color/depth/stencil buffers, and sets up an orthographic projection matching the screen. Then draw your scene and call gl_present() to blit to the display.
Source Files
| File | Purpose |
|---|---|
gl.c / gl.h |
Top-level API, glGetString |
init.c |
Initialization, framebuffer setup |
rasterizer.c/.h |
Triangle/line/point rasterization |
vertex.c/.h |
Vertex transform, fetching, interpolation |
fragment.c/.h |
Fragment shader, texture sampling, lighting |
texture.c/.h |
Texture objects, mipmapping |
lighting.c/.h |
Normal matrix, Phong lighting |
math.c/.h |
Matrix/vector math |
state.c/.h |
GL state machine, type definitions |
framebuffer.c/.h |
FBO/RBO management |
present.c/.h |
GL → screen framebuffer conversion |
buffer.c/.h |
VBO management, vertex attrib pointers |
displaylist.c/.h |
Display list recording/playback |
stats.c/.h |
Performance counters |
api_*.c/.h |
OpenGL function implementations |