With the original goal of building a small game completely from scratch as a learning exercise, this project became my most extensive venture into engine architecture and rendering techniques, still far from finished and primarily a hobby project where I can have fun experimenting with engine code.
Ever since my C++ classes in college, I have wanted to try my hand at creating my own rendering code. Over the years I tried my hand at following the OpenGL and Vulkan tutorials, built some projects on top of SDL or SFML, and I even tried some tutorials for creating a raytracer and a custom Unity rendering pipeline. When I was tinkering with Unity’s rendering API during my graduation project, I was inspired to commit for real and try to build an efficient, fast renderer to be used for a small 3D game.
Since September 2025 I have been building out this project into a full-fledged renderer and game engine built around flecs, a powerful and lightweight ECS framework, with a modern OpenGL renderer I built from the ground up. The project features automatic instanced rendering, stable asset management, a custom math library, a shader reflection system, SDF font rendering, and more (to come).
While the primary goal is to create a framework capable of running a small game (tower defense), I treat it as a hobby project where I focus on the things that I enjoy implementing and exploring. I care about creating a solidly built codebase, exploring ways to optimize both code and workflow, and gaining a solid understanding of graphics APIs and GPU programming. The project is still in active development, and will only be done when I stop having fun with it.
This project lies somewhere between framework and engine. I want to provide all the core functionality of an engine, like asset management, rendering, world management, etc., and wrap it in the package of a framework with no graphical interface. I maintain a clear separation between engine code and game code. For managing the virtual world, I rely solely on flecs. The engine hooks into this occasionally, but also maintains its own, separate systems where I deemed this more appropriate.
I use flecs for managing the virtual (game) world. It will be responsible for all game systems, and I also use it to run bulk systems in preparation of rendering. The rendering itself is not done directly out of flecs; it is capable of doing so but this has its own disadvantages.
Flecs offers lockless multithreading out of the box for compatible systems operating on large amounts of entities. I currently leverage this for things like culling (see screenshot below), with more planned later.

Every resource used by the game is considered an asset. Assets are currently loaded on demand, but in the future I will be implementing asset packs directly usable in the engine, that are loaded once at the start of the game. The system supports static assets (files directly from the hard drive), but also runtime assets that are created by the engine at runtime.
Assets are stored in a pointer-stable container managed by the engine, allowing for a fast and safe system of referencing assets from anywhere in the codebase based on unique identifiers. This makes the asset system very useful for storing all resources that need to exist for longer than a few frames or in very contained modules.
Rendering is done with OpenGL 4.6, the latest and (likely) final version of the library. With speed as a first class goal, I try to implement the most modern rendering techniques as this aging library allows. (Yes, it is making me appreciate next-gen graphics APIs very fast.)
The renderer uses instancing as a first-class approach. The renderer automatically batches calls with the same mesh, material, and shader into a single instanced call, without the user needing to intervene. I use global shader storage buffers to get the necessary data to the GPU. Below is a screenshot of around 400k instances being rendered at ~30fps.

The material system is based on an internal model built from shader reflection. Data can be set with simple shorthand methods like myMaterial.setFloat("myShaderProperty"_spid, 3.4f). There is no runtime string lookup; shader property strings suffixed with _spid are converted to integers at compile time for faster runtime lookup.
Shaders, materials, and the renderer make extensive use of shader storage and uniform buffers to reduce GPU-CPU link bandwidth per frame. Data is updated in very modular blocks, without any more uploads than strictly necessary.
The renderer supports post-processing with fullscreen shaders and a post-processing stack. Everything is rendered in linear color space and converted to sRGB as well as gamma-corrected during post-processing. The renderer supports an arbitrary stack of post-processing effects that can dynamically be updated. Effect settings can also dynamically be changed during each frame at no additional cost.
I implemented font rendering based on the signed distance field technique provided by msdfgen. My implementation leverages the globally available automatic instancing to render large amounts of text in a single instanced draw call. By using an auxiliary buffer to pass glyph data to the GPU, I am not only able to pass UV-coordinates on the glyph atlas along to the GPU, but also implement as many custom effects per character as I want to. I am currently generating glyph atlases at startup, but plan to extract this into a pre-build step to speed up application startup. I also want to add better support for the full UTF-8 character set, since the current support for Latin and Latin-extended 1 is fairly limiting. Other plans are to either use or implement a better layout engine, that supports alignment, kerning, ligatures, and more.

A major aspect of development is debugging. For games this often works a little differently from normal applications, since all code is supposed to run in a constant loop, and many validation tools work best when actually visualized. My debugging suite is still in very early development, but I already implemented several features and external tools:

I plan to expand the debugging features extensively at some point, though I will look into this on an as-needed basis to not lose myself in these fun, but challenging features.