PortableGL-rs

March 16, 2026 · View on GitHub

crates.io CI license

"Because of the nature of Moore's law, anything that an extremely clever graphics programmer can do at one point can be replicated by a merely competent programmer some number of years later." -John Carmack

PortableGL-rs is a Rust port of PortableGL, an implementation of OpenGL 3.x core (mostly; see GL Version) as a pure software renderer. No GPU required.

The original is a ~13,000 line single-header C99 library. This port rewrites it in idiomatic Rust while maintaining functional equivalence, and can also serve as a drop-in C library replacement via an optional FFI layer.

It can be used with anything that takes a 32 or 16 bit framebuffer/texture as input in any format (including just writing images to disk or blitting raw pixels to the screen via SDL2, win32, X11, etc.).

It supports arbitrary 32- and 16-bit color buffer formats (selected at compile time via Cargo features) with several common ones ready to use out of the box.

Goals (inherited from the original project, roughly in order of priority):

  1. Portability
  2. Matching the OpenGL API within reason
  3. Ease of Use
  4. Straightforward code
  5. Speed

Like the original, shaders are native function pointers (Rust fn / C function pointers) rather than GLSL strings. Uniforms are passed as a raw *mut c_void pointer to a user-defined struct.

Features

  • Pure software OpenGL 3.x core profile renderer (no GPU, no platform dependencies)
  • Vertex processing with configurable attributes and instancing
  • Triangle rasterization with barycentric coordinates
  • Sutherland-Hodgman triangle clipping against 6 frustum planes
  • Cohen-Sutherland parametric line clipping
  • Perspective-correct, noperspective, and flat interpolation
  • Depth testing, stencil testing, scissor testing
  • Alpha blending with separate RGB/Alpha equations
  • Face culling, polygon modes (fill/line/point)
  • 1D/2D/3D textures, cubemaps, texture arrays, rectangle textures
  • Nearest and linear (bilinear/trilinear) texture filtering
  • Texture wrap modes: repeat, clamp to edge, clamp to border, mirrored repeat
  • Bresenham and Wu's anti-aliased line drawing
  • Point sprites with gl_PointCoord
  • Polygon offset (depth bias)
  • Logic operations
  • Monomorphized shader dispatch via FragmentShader trait (LLVM inlines shaders into pixel loops)
  • no_std support (with alloc)
  • C FFI layer for drop-in replacement of the original C library

Differences from the C Version

AspectC (PortableGL)Rust (PortableGL-rs)
LanguageC99, single-header libraryRust 2021 edition, multi-module crate
API styleFree functions with global glContext*Methods on GlContext struct
ShadersC function pointersunsafe extern "C" fn pointers (compatible with both Rust and C callers)
MemoryManual (malloc/free, cvector)Vec<T>, RAII, ownership
Pixel formatPreprocessor macrosCargo feature flags
Build systemMakefile / single header includecargo build
no_stdOverridable alloc via macros; only requires math.h/stdint.hSupported via no_std feature + alloc
C FFINativeOptional via ffi feature flag
Thread safetyGlobal mutable stateOwned GlContext per thread (FFI uses global for C compat)

Getting Started

As a Rust Library

Add to your Cargo.toml:

[dependencies]
portablegl = "0.8"

Basic usage:

use portablegl::gl_context::GlContext;
use portablegl::gl_types::*;
use portablegl::math::*;
use core::ffi::c_void;

// Define a vertex shader
unsafe extern "C" fn my_vs(
    vs_output: *mut f32,
    vertex_attribs: *mut Vec4,
    builtins: *mut ShaderBuiltins,
    uniforms: *mut c_void,
) {
    (*builtins).gl_Position = *vertex_attribs;
}

// Define a fragment shader
unsafe extern "C" fn my_fs(
    fs_input: *mut f32,
    builtins: *mut ShaderBuiltins,
    uniforms: *mut c_void,
) {
    (*builtins).gl_FragColor = Vec4::new(1.0, 0.0, 0.0, 1.0);
}

fn main() {
    let mut ctx = GlContext::new();
    let pixels = ctx.init(640, 480);

    // Create and use a shader program
    let program = ctx.pgl_create_program(my_vs, my_fs, 0, &[], false);
    ctx.gl_use_program(program);

    // Set up geometry, draw, etc.
    ctx.gl_clear_color(0.0, 0.0, 0.0, 1.0);
    ctx.gl_clear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

    // pixels Vec<u32> contains the rendered framebuffer
}

Monomorphized Shaders (Advanced)

For maximum performance, implement the FragmentShader trait on a concrete type. LLVM inlines the shader body directly into the rasterization loop, eliminating per-pixel indirect call overhead:

use portablegl::gl_types::*;
use portablegl::math::*;

struct RedShader;

impl FragmentShader for RedShader {
    #[inline(always)]
    unsafe fn shade(&self, _fs_input: *mut f32, builtins: *mut ShaderBuiltins) {
        (*builtins).gl_FragColor = Vec4::new(1.0, 0.0, 0.0, 1.0);
    }
}

// Use instead of gl_draw_arrays for triangle modes:
ctx.gl_draw_arrays_with_fs(GL_TRIANGLES, 0, count, &RedShader);

This is a non-breaking opt-in — the standard gl_draw_arrays API works unchanged.

As a C Drop-in Replacement

Build with the ffi feature to produce a shared/static library:

cargo build --features ffi --release

This produces:

  • portablegl.dll / libportablegl.so (shared library)
  • libportablegl.a (static library)

Link against it from C/C++ and use the same API as the original PortableGL:

#include <stdint.h>
// Use the same function names as PortableGL
extern GLboolean init_glContext(glContext* c, uint32_t** back, int w, int h);
extern void set_glContext(glContext* c);
extern void glClearColor(float r, float g, float b, float a);
extern void glClear(unsigned int mask);
// ... etc.

For no_std Environments

[dependencies]
portablegl = { version = "0.8", features = ["no_std"] }

Requires an allocator (alloc crate). Uses libm for float math. Works on embedded targets, custom OS kernels, WASM without std, etc.

Running Examples

Three interactive examples are included (using SDL2 for windowing):

cargo run --example hello_triangle --features examples      # Solid red triangle
cargo run --example colored_triangle --features examples    # RGB interpolated triangle
cargo run --example spinning_triangle --features examples   # Rotating 3D triangle

Cargo Features

FeatureDescriptionDefault
abgr32ABGR 32-bit pixel formatYes
rgba32RGBA 32-bit pixel format
argb32ARGB 32-bit pixel format
bgra32BGRA 32-bit pixel format
rgb565RGB 16-bit pixel format
bgr565BGR 16-bit pixel format
d24s824-bit depth + 8-bit stencilYes
d1616-bit depth, no stencil
no_depthNo depth buffer
no_stdno_std + alloc support (uses libm)
no_stencilDisable stencil buffer
ffiEnable C FFI wrappers
disable_color_maskSkip per-pixel color mask (matches C PGL_DISABLE_COLOR_MASK)
hermite_smoothingUse Hermite smoothing for interpolation
better_thick_linesImproved thick line rendering
examplesBuild interactive examples and benchmarks (uses SDL2)

Project Structure

src/
  lib.rs          - Crate root, module declarations
  math.rs         - Vec2/3/4, Mat3/4, Color, Line types and operations
  gl_types.rs     - GL type aliases, 313 constants, core structs
  gl_context.rs   - GlContext state machine struct
  gl_impl.rs      - GL API implementation (buffers, textures, VAOs, draw calls, state)
  gl_internal.rs  - Core pipeline (clipping, rasterization, fragment processing)
  gl_glsl.rs      - Texture sampling functions (1D/2D/3D, cubemap, array, fetch)
  pgl_ext.rs      - PGL extensions (clear screen, draw frame, format conversion)
  float_math.rs   - Float math compatibility layer for no_std
  ffi.rs          - C FFI wrappers (behind "ffi" feature)

GL Version

Same as the original PortableGL: mostly OpenGL 3.x core profile, with some 4.x DSA functions and compatibility profile features (like a default VAO) included where they come for free in a software renderer. See the original README for details.

Documentation

The best way to learn is to look at the original PortableGL examples and demos, as well as the LearnPortableGL tutorials.

The official OpenGL reference pages cover 90-95% of the API usage.

Real-World Example: Craft-rs

Craft-rs is a Rust port of Craft (a Minecraft clone by Michael Fogleman) that uses PortableGL-rs for all rendering — no GPU required. It demonstrates the library in a real-world application with textured voxel terrain, ambient occlusion, day/night cycle, procedural world generation, and multiple shader programs (block, sky, text, line).

Performance Benchmarks

Comparative benchmarks between the original C PortableGL and this Rust port, using identical test scenes and SDL2 for display/timing. All 11 benchmarks are ported 1:1 from the upstream testing/performance_tests.cpp.

Test System

  • CPU: Intel Core i5-12400F (6 cores / 12 threads)
  • RAM: 32 GB DDR4-3200
  • OS: Windows 10 LTSC (Build 19044)
  • C compiler: g++ 15.2.0 (MinGW-w64, -O2 -ffp-contract=off)
  • Rust compiler: rustc 1.94.0 (--release)

Results

BenchmarkC (FPS)Rust (FPS)RatioDescription
points_perf1084.0750.30.69x12,000 points, size 1
pointsize_perf1731.31268.80.73x~857 points, size 4
lines_perf274.7270.40.98x1,000 lines, width 1
lines8_perf42.547.81.12x1,000 lines, width 8
lines16_perf21.724.21.12x1,000 lines, width 16
triangles_perf23.732.01.35x50 random triangles
tri_interp_perf42.139.60.94x30 smooth-shaded triangles
tri_clipxy_perf680.6692.11.02x20 triangles, XY clipping
tri_clipz_perf160.0198.71.24x15 triangles, Z clipping
tri_clipxyz_perf272.8328.11.20x50 triangles, XYZ clipping
blend_perf328.4291.60.89xAlpha blending (9 quads)

Values are the average of two consecutive runs. Bold ratios indicate Rust is faster.

Running the Benchmarks

# Rust benchmarks (all tests)
cargo run --example perf_tests --features examples --release

# Rust benchmarks (specific test)
cargo run --example perf_tests --features examples --release -- triangles_perf

# C benchmarks (from the PortableGL repo)
cd PortableGL/testing
make -f perf_tests.make config=release
./perf_tests

Analysis

The Rust port matches or exceeds C performance in 7 of 11 benchmarks, with no unsafe code in the hot pixel paths. The remaining gaps are in point rendering and blending, where the C version benefits from PGL_UNSAFE and PGL_DISABLE_COLOR_MASK compile-time elisions. The Rust disable_color_mask feature provides an equivalent elision for the color mask operation.

Similar/Related Projects

  • PortableGL - The original C99 implementation this is ported from.
  • Craft-rs - Minecraft clone running on PortableGL-rs (Rust port of Craft).
  • pgl - A Go port of PortableGL.
  • TinyGL - Fabrice Bellard's OpenGL 1.x subset implementation.
  • Mesa3D - Full open source OpenGL/Vulkan implementation with software renderers.
  • SoftGLRender - OpenGL software renderer in modern C++.

Acknowledgments

This project was ported from C to Rust with the assistance of Claude Opus 4.6 (Anthropic). Claude helped translate the ~13,000-line C99 codebase into idiomatic Rust, implement the no_std support layer, C FFI bindings, and port the regression test suite — achieving full functional equivalence with the original PortableGL.

LICENSE

PortableGL-rs is licensed under the MIT License (MIT).

The code used for clipping is copyright (c) Fabrice Bellard from TinyGL, also under the MIT License. See LICENSE.