A from-scratch 2D software renderer. No GPU, no OpenGL — just pixels on a CPU-resident framebuffer, presented through minifb.
- Lines (Bresenham + Cohen-Sutherland clipping), thick lines via polygon fill
- Rects, circles (midpoint), ellipses (midpoint, two-region Bresenham)
- Triangles and polygons (scanline fill with even-odd rule)
- Rotated rects (
_exsuffix)
- Blend modes:
None,Alpha(Porter-Duff source-over, integer-only),Additive - Sampler filter:
Nearest,Bilinear(2×2 lerp) - Wrap mode:
Clamp,Repeat,Mirror - State lives on
Renderer2D; all dispatch is compile-time via templated internals
- Load PNG/JPG via stb_image (RGBA → ARGB conversion at load time) or build procedurally from a pixel buffer via
Texture::from_pixels blit(axis-aligned, pixel-exact) andblit_ex(scale + rotate + sampler)- Texture-space origin convention (pivot lands on
pos), consistent with raylib/SFML
Vec<T, N>generic vector with deducing-this, structured bindings, broadcast ctorColor(ARGB8888) with integer-only blend, staticlerpAABB2Dwith clipping and intersectionstd::expected<T, Error>for all fallible operations (FrameBuffer::create,Texture::load,Window::create)
Windowover minifb: present, is_open, target FPS, FPS counter in title- Input: keyboard (down/pressed/released edges), mouse buttons, position, scroll
- Delta time per frame
sr::core — types, vec, color, aabb, error
sr::gfx
raster:: — free functions, low-level primitives taking FrameBuffer&
Renderer2D — stateful API over raster:: (holds blend/sampler state)
FrameBuffer — owning pixel buffer (Pixel = u32 ARGB)
Texture — immutable image, loaded from file or built from pixel buffer
sr::platform — Window, Input
raster:: is stateless; Renderer2D wraps it with render state. Public functions take runtime enum parameters; internally they dispatch to templated implementations (write_pixel<M>, sample<F, W>, blit_ex_impl<M, F, W>) so hot loops contain zero enum-checks.
FrameBuffer distinguishes checked public API from *_unchecked internals used after clipping; every _unchecked method asserts its precondition in debug.
SR_INLINE / SR_NOINLINE macros (in sr/core/macros.h) wrap compiler-specific always_inline / noinline attributes; used on hot-path template helpers in the rasterizer where GCC's inliner heuristic is too conservative.
cmake -B build -DCMAKE_BUILD_TYPE=Release
cmake --build build
./build/demos/01_primitives/demo_01_primitives
./build/demos/02_sprites/demo_02_sprites
./build/demos/03_benchmark/demo_03_benchmark
./build/demos/04_paint/demo_04_paintRequires C++23 (GCC 14+ or Clang 18+). minifb is bundled under thirdparty/.
01_primitives— grid of all primitive types (filled/outlined, rotated).02_sprites—blit+blit_exvariants: scaled, rotated, scaled+rotated, mirrored, half-size.03_benchmark— heavy deterministic scene (~5600 moving primitives + 100 textured sprites over 4 procedural textures). Fixed xorshift32 seed for reproducibility; intended as a baseline for the Rust port.04_paint— interactive paint demo: LMB paints with a stamped-circle brush, RMB erases, 1-6 picks color,[/]adjusts thickness,Cclears.
2D MVP complete and hardened. Current renderer state + roadmap:
- All primitives with alpha blending
- Textures with nearest/bilinear sampling and clamp/repeat/mirror wrap
- BlendMode (None / Alpha / Additive)
- Clip rect (scissor) on FrameBuffer
- Tile-parallel rasterizer
- 3D pipeline: Mat4, vertex/index buffers, MVP transform, z-buffer
- Programmable shaders (vertex + fragment as functors)
- Render targets / mip-maps
- SIMD rasterizer (edge functions, 2×2 quads)
See TODO.md for tracked issues.
Scene graph, material system with configs, GPU/RHI abstraction. The renderer stays stateless "give me draw calls, I rasterize" — no engine creep.