FireGL
Cross-platform rendering library focused on safe resource handling, modern C++ abstractions, and practical OpenGL workflows.
- C++20
- OpenGL
- CMake
- GLFW
Motivation
I wanted to learn how rendering systems work under the hood by building one myself. Coming from Unreal Engine, I was curious to recreate a simplified version of a rendering workflow to better understand what happens behind high-level engine abstractions.
Approach
I explored how a modular OpenGL rendering architecture can be structured in C++ by separating rendering logic, resource management, and application-level code.
The goal was not to build a production engine, but to understand how core graphics systems are designed: how GPU resources are created, managed, and used in a rendering loop.
Technology used
- C++20 — used to practice modern C++ design patterns and structured resource management concepts.
- OpenGL — learned the graphics pipeline (shaders, buffers, draw calls, state management).
- GLFW — window creation, OpenGL context setup, and input handling.
- GLAD — OpenGL function loading.
- GLM — math for transforms, vectors, and camera operations.
- STB Image — texture loading and image decoding.
- Assimp — importing and handling 3D model data.
- CMake — cross-platform build system and dependency management.
Key things I worked on / learned
- How an OpenGL context is initialized and tied to a windowing system (GLFW).
- How shaders are compiled, linked, and used in a rendering pipeline.
- How GPU resources (buffers, textures, meshes) are created, bound, and managed.
- How a basic render loop is structured (input → update → render).
- How to design a simple abstraction layer over raw OpenGL calls.
- How asset loading pipelines work using STB Image and Assimp.
- How to structure a small engine-like architecture separating core rendering from application logic.
- How external C++ graphics dependencies integrate through CMake.
Engine usage (application layer)
A simple FireGL application that sets up a window, loads assets, builds a scene, and runs a real-time rendering loop.
#include <FireGL/FireGL.h>
int main()
{
fgl::AssetPathManager Assets(PROJECT_ROOT + "Config.ini");
fgl::BaseWindow Window;
Window.Initialize(4, 1, "Test123", fgl::WindowType::Windowed, false, 800, 700);
Window.SetWindowIcon(Assets.GetPath("Troll"));
fgl::TimeManager Timer;
Timer.Initialize();
auto Camera = std::make_shared<CustomCamera>();
Camera->SetPerspective(45.f, Window.GetAspectRatio(), 0.1f, 1000.f);
CustomInputManager Input(Camera);
Input.Initialize();
fgl::Shader Shader(
Assets.GetPath("BaseLightingVertex"),
Assets.GetPath("BaseLightingFragment")
);
fgl::Texture Texture;
Texture.LoadTexture(Assets.GetPath("Wood"));
auto Material = std::make_shared<fgl::LightingMaterial>(&Shader);
Material->SetTexture("normal", &Texture);
auto Model = std::make_shared<fgl::Model>(
Assets.GetPath("BackpackModel")
);
Model->SetupMaterial<fgl::LightingMaterial>(&Shader);
auto Entity = std::make_unique<fgl::Entity>(Model);
fgl::Scene Scene(Camera);
Scene.AddObject(std::move(Entity));
fgl::Renderer Renderer(fgl::RenderingMode::Default);
while (!Window.ShouldClose())
{
Timer.Update();
Input.ProcessInput();
Scene.Process();
Renderer.Render(&Scene);
Input.FinalizeInput();
}
Window.Terminate();
return 0;
}
Engine internals (Entity system)
Custom Entity system inspired by game engine architecture (Unreal-style lifecycle + component model)
class Entity : public SceneObject
{
public:
template <typename T,
std::enable_if_t<std::is_base_of_v<SceneObject, T>, bool> = true>
Entity(std::shared_ptr<T> Object);
void SetMaterial(std::shared_ptr<Material> Material) override final;
const std::shared_ptr<Material> GetMaterial(size_t MeshIndex = 0) const override final;
std::vector<BaseMesh>& GetMeshes() override;
size_t GetHash() const override;
template <typename T> T* CreateComponent();
template <typename T> T* GetComponent();
void RemoveComponent(size_t ID);
protected:
virtual void OnTick(float DeltaTime);
virtual void OnBeginPlay();
virtual void OnDestroyed();
virtual void OnPrepareRender() const;
virtual void OnPostRender() const;
private:
void Render(size_t NumberInstance) const override final;
void Tick(float DeltaTime) override final;
void BeginPlay() override final;
void Destroy() override final;
private:
std::shared_ptr<SceneObject> m_Object;
std::unordered_map<size_t, std::unique_ptr<Component>> m_Components;
};
Note
This project was valuable for learning core rendering and graphics programming concepts in OpenGL and C++. However, I also realized that I over-abstracted parts of the rendering architecture too early, which made the system more complex than necessary for its scale.
As a result, while it works well as an experimental learning project, it would require significant redesign to become a truly scalable or production-ready rendering engine.